从一个函数中返回char*和返回char[]有什么区别?

69
为什么第一个函数返回字符串"Hello, World"而第二个函数没有返回任何内容。我认为两个函数的返回值都应该是未定义的,因为它们返回超出作用域的数据。
为什么第一个函数返回字符串"Hello, World"而第二个函数没有返回任何内容。我认为两个函数的返回值都应该是未定义的,因为它们返回超出作用域的数据。
#include <stdio.h>
// This successfully returns "Hello, World"
char* function1()
{
    char* string = "Hello, World!";
    return string;
}
// This returns nothing
char* function2()
{
    char string[] = "Hello, World!";
    return string; 
}

int main()
{
    char* foo1 = function1();
    printf("%s\n", foo1); // Prints "Hello, World"
    printf("------------\n");
    char* foo2 = function2(); // Prints nothing
    printf("%s\n", foo2);
    return 0;
}

9
@Leushenko,不要这样想。这里的问题是关于函数返回值,而不仅仅是char[]char *之间的区别。 - machine_1
33
我厌倦这里的管理员们过于强硬地宣称那些写得好的新手问题都是重复的。问问题的人已经了解足够的 C 语言知识,以一种有助于自己的方式提出了问题。将以其他方式提出的问题“翻译”成为另一种问题通常需要已经掌握了问问题者正试图开发的技能。 - verisimilidude
26
“重复”并不等同于“问题不好”,与其他关闭投票不同,它并不意味着用户的问题无效。对我来说,这个问题是一个罕见的“点赞+关闭”组合案例:这是一个很好的第一个问题,但它需要属于更大的“数组变量与指针”系列问题以便得到全面理解,而答案背后的基本真理将是相同的。 - Alex Celeste
11
将所有类似的问题链接在一起的想法是帮助人们在一个地方找到所有好的答案。至少这有助于谷歌确定应该将哪个帖子排在首位。 - Bo Persson
显示剩余4条评论
6个回答

71

第二个函数没有返回任何东西

第二个函数中的 string 数组:

char string[] = "Hello, World!";

具有自动存储期。在控制流从函数中返回后,它将不存在。

而第一个函数中的string

char* string = "Hello, World!";

指向一个具有静态存储期的文字字符串。这意味着,在函数返回后,该字符串仍然存在。你从函数返回的是指向这个文字字符串的指针。


28
我认为这句话需要进一步澄清。在两种情况下,字符串“Hello, world!”具有静态存储期。第一种情况下,我们创建了一个自动数组称为“string”,并将静态字符串复制到自动数组中。在第二种情况下,我们创建了一个自动指针称为“string”,它指向静态存储区域。 - M.M
1
没错。将该行改为static char string[] = "Hello, world!";,看看会发生什么。这将把数组的存储期从自动变为静态。(此外,你应该养成声明不需要修改的数据为const的习惯。) - Davislor
1
这是否意味着所有的 char* 都是静态的?如果我写了 static char* string = "Hello, World!",那会改变什么吗? - Tobs
1
那么这会使string成为一个指向字符串字面量的静态指针,在这种情况下,它只是浪费了一些内存。如果你再次调用相同的函数,static变量仍然存在并且已经被设置。在这种情况下,char* stringchar string[]之间的区别是前者指向字符串常量,而后者在不同的内存块中创建了一个临时副本。通常这样做是为了能够修改该副本。将该副本声明为static可以使该数组在函数退出后仍然存在。即使函数返回后,它仍然有效。 - Davislor
2
字符串字面量(例如“Hello, world!”)具有静态存储,这是事实。由于历史原因,字符串字面量有点奇怪。只有在char*中而不是const char*中存储它们才是合法的,以便与在const存在之前编写的代码向后兼容。但是,当您尝试通过非const指针修改字符串时,这会导致错误。最好将其声明为const char* const,因为它是指向不可修改内存的永远不会被修改的指针。 - Davislor
@眠りネロク 我认为将那个最受欢迎的评论中的信息添加到你其他优秀的答案中会非常值得。 - Dúthomhas

27

关于字符串的第一件事是,字符串字面量实际上是一个具有全局生存期的只读字符数组。这意味着它们永远不会超出范围,在程序执行期间始终存在。

第一个函数 (function1) 的作用是返回指向这种数组第一个元素的指针。

第二个函数 (function2) 有些不同。这里变量string是函数内部的局部变量。因此,一旦函数返回,它就会超出范围并停止存在。通过这个函数,您返回指向该数组第一个元素的指针,但是该指针将立即变为无效,因为它将指向不再存在的东西。对它进行解引用(当你将它传递给printf)将导致未定义行为


5
具体来说,在function2中还存在一个只读字符串,该字符串将在函数退出后继续存在。但是您没有返回指向它的指针。发生的情况是将数据从该字符串复制到本地变量,然后返回指向该变量的指针。因此,该字符串仍然存在于某个位置,只是不在指针指向的位置。 - ComicSansMS
2
@ComicSansMS 可能没有只读字符串。编译器往往会将这样的赋值优化为移动少量数字而不是复制字符串。 - bezet
1
@bezet 我想答案可能更准确地使用规范喜欢的“as if”术语来表达,但我认为在这种情况下我们可以不用它。编译器在解释您的代码时会有一个只读字符串的“思维”,无论该字符串是否以逐字方式发出到可执行文件中都由优化器确定。 - Cort Ammon

7
在使用C语言或其他基于堆栈的语言编写代码时,一个非常重要的事情要记住:当函数返回时,它(以及所有的本地存储)都不存在了。这意味着如果你想让其他人能够看到你方法的辛勤工作的结果,你必须把它放在一个在你的方法停止后仍然存在的地方,而要做到这一点就需要了解C存储东西的方式和位置。
您可能已经知道C中数组的操作方式。它只是一个内存地址,其大小为对象的大小,并且您可能还知道C不会进行边界检查,因此如果您想访问十个元素数组的第11个元素,则没有人会阻止您,只要您不尝试编写任何内容,就不会造成伤害。但您可能不知道的是,C将这种思想扩展到了它使用函数和变量的方式上。函数只是一个栈上的内存区域,按需加载,用于存储其变量的存储器只是该位置偏移量。你的函数返回了一个指向局部变量的指针,具体来说,就是'H'的位置,而当你调用另一个函数(打印方法)时,该内存被打印方法重复使用。您可以很容易地看到这一点(不要在生产代码中这样做!!!)
char* foo2 = function2(); // Prints nothing
ch = foo2[0];  // Do not do this in live code!
printf("%s\n", foo2);  // stack used by foo2 now used by print()
printf("ch is %c\n", ch);  // will have the value 'H'!

是的,即使它在静态字符串上有点用处,这也是一个非常不好的习惯(在我看来)。 - jamesqf

5
我认为这两个函数的返回值应该是未定义的,因为它们返回了超出作用域的数据。
不,情况并非如此。
在函数function1中,您正在返回指向字符串文字的指针。返回指向字符串文字的指针是可以的,因为字符串文字具有静态存储期。但对于自动局部变量来说并非如此。
在函数function2中,数组string是自动局部变量,语句
return string; 

返回自动局部变量的指针。一旦函数返回,变量 string 将不再存在。对返回的指针进行解引用将导致未定义的行为。


@Jean-FrançoisFabre; 我错了。字符串字面量具有静态存储期,因此从函数返回指向它的指针是可以的。 - haccks
1
是的,我知道那个,但今天有人学到了新东西 :) - Jean-François Fabre
3
@Jean-FrançoisFabre; 是的,这就是在为社区做出贡献时除了声望之外得到的回报。 - haccks
1
这就是我喜欢这里的原因。你要么学习,要么得到声誉。有时候两者兼备 :) - Jean-François Fabre

1
"Hello, World!"是一个字符串字面量,具有静态存储期,因此问题在其他地方。您的第一个函数返回string,这很好。但是第二个函数返回本地变量的地址string&string[0]相同),导致未定义的行为。您的第二个printf语句可能打印什么也没有,或者打印"Hello, World!",或者完全不同的内容。在我的机器上,程序只会得到一个分段错误。

始终查看编译器输出的消息。对于您的示例,gcc给出:

"
file.c:12:12: warning: function returns address of local variable [-Wreturn-local-addr]
    return string; 
           ^

这几乎是不言自明的。


0
我认为这两个函数的返回值都将是未定义的,因为它们返回的数据已经超出了作用域。
这两个函数都返回一个指针。重要的是指向对象的作用域。
在`function1`中,所指对象是静态储存期的字符串字面常量"Hello, World!"。局部变量`string`指向该字符串,并且概念上会返回该指针的一个拷贝(实际上编译器会避免不必要的拷贝)。
在`function2`中,概念上指向对象是局部数组`string`,该数组被自动分配大小(在编译时)足以容纳字符串字面常量(当然包括空终止符),并被初始化为该字符串的副本。该函数本应返回该数组的指针,但由于该数组具有自动储存期,因此在退出函数后即不存在(更熟悉的术语是"超出了作用域")。由于这是未定义行为,因此编译器可能会执行各种操作
这是否意味着所有的char*都是静态的?再次强调,你需要区分指针和指向的对象。指针指向数据,它们本身并不“包含”数据。你已经到了一个需要仔细学习C语言中数组和指针的实际含义的地步——不幸的是,这有点混乱。我可以提供的最好的参考资料是this,以问答形式呈现。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接