为什么我们可以从函数中 return char* ?

Why can we return char* from function?

这是一段 C++ 代码,显示了一些非常奇特的行为。谁能告诉我为什么strB可以打印出这些东西?

char* strA()
{
    char str[] = "hello word";
    return str;
}

char* strB()
{
    char* str = "hello word";
    return str;
}

int main()
{ 
    cout<<strA()<<endl;  
    cout<<strB()<<endl;
}
                      
                                                                                                                                                            

String literals 在程序的生命周期内存在。

String literals have static storage duration, and thus exist in memory for the life of the program.

这意味着 cout<<strB()<<endl; 没问题,指向字符串文字 "hello word" 的 returned 指针仍然有效。

另一方面,cout<<strA()<<endl;通向UB。 returned 指针指向本地数组的第一个元素 str;当 strA() returns 时被销毁,使 returned 指针悬空。


顺便说一句:字符串文字的类型为 const char[]char* str = "hello word"; 再次从 C++11 开始无效。将其更改为const char* str = "hello word";,并将strB()的return类型也更改为const char*

String literals are not convertible or assignable to non-const CharT*. An explicit cast (e.g. const_cast) must be used if such conversion is wanted. (since C++11)

为什么 strB() 有效?

字符串文字(例如"a string literal")具有静态存储持续时间。这意味着它的生命周期跨越了程序执行的持续时间。这是可以做到的,因为编译器知道您将在程序中使用的 每个 字符串文字,因此它可以将它们的数据直接存储到已编译可执行文件的数据部分(示例:https://godbolt.org/z/7nErYe)

当您获得指向它的指针时,该指针可以自由传递(包括从函数 return 编辑)并取消引用,因为它指向的对象始终处于活动状态。

为什么 strA() 不起作用?

但是,从字符串文字初始化 char 数组会复制字符串文字的内容。创建的数组是与原始字符串文字不同的对象。如果这样的数组是一个局部变量(即具有自动存储持续时间),如在您的 strA() 中,那么它在函数 returns.

之后被销毁

当您从 strA() return 时,由于 return 类型是 char* 执行“数组到指针转换”,创建指向数组的第一个元素。但是,由于数组在函数returns时被销毁,指针returned就失效了。您不应该尝试取消引用此类指针(并首先避免创建它们)。

案例 1:

#include <stdio.h>

char *strA() {
    char str[] = "hello world";
    return str;
}

int main(int argc, char **argv) {
    puts(strA());
    return 0;
}

语句 char str[] = "hello world"; 在调用时(可能)被放入堆栈,并在函数退出后过期。如果你天真地认为这就是它在所有目标系统上的工作方式,你可以像这样编写可爱的代码,因为延续是在现有堆栈的顶部调用的(所以函数的数据仍然存在,因为它没有return尚未编辑):

你可以继续作弊:

#include <stdio.h>

void strA(void (*continuation)(char *)) {
    char str[] = "hello world";
    continuation(str);
}

void myContinuation(char *arg) {
    puts(arg);
}

int main(int argc, char **argv) {
    strA(myContinuation);
    return 0;
}

案例2: 如果您使用下面的代码片段,文字“hello world”通常存储在受保护的只读内存中(尝试修改此字符串会在许多系统上导致分段错误,这类似于您的 main 和 strA 的存储方式,c 代码基本上只是一个 instructions/memory blob 的字符串,就像字符串是一串字符一样,但我离题了),即使您从未调用该函数,该字符串也将对程序可用知道它应该在特定系统上的地址。在下面的代码片段中,程序甚至没有调用函数就打印了字符串,这通常可以在相同的平台上工作,使用相对相同的代码和相同的编译器。虽然它被认为是未定义的行为。

#include <stdio.h>

char *strB() {
    char *str = "hello world";
    return str;
}

int main(int argc, char **argv) {
    char *myStr;

    // comment the line below and replace it with
    // result of &myStr[0], in my case, result of &myStr[0] is 4231168
    printf("is your string: %s.\n", (char *)4231168);
    myStr = strB();
    printf("str is at: %lld\n", &myStr[0]);
    return 0;
}

您可以选择使用结构和相对安全的 strC。此结构在堆栈上创建并完全 returned。 strC 的 return 大小为 81(我为结构编造的任意数字,我相信自己会尊重)字节。

#include <stdio.h>

typedef struct {
     char data[81];
} MY_STRING;

MY_STRING strC() {
    MY_STRING str = {"what year is this?"};
    return str;
}
    
int main(int argc, char **argv) {
    puts(strC().data);
    printf("size of strC's return: %d.\n", sizeof(strC()));

    return 0;
}
  

tldr; strB 一旦从函数中 returns 就可能被 printf 损坏(因为 printf 现在有自己的堆栈),而 strA 中使用的字符串存在于函数外部,它基本上是一个指向全局常量的指针,可用作程序启动后(字符串在内存中与代码在内存中的方式没有区别)。