gcc 是否将长字符串初始化为 `""` 而不是短字符串?

Joh*_*ers 2 c gcc undefined-behavior

注意: 我知道读取未初始化的字符串是未定义的行为。这个问题具体是关于 GCC 的实施。

\n

我使用的是 GCC 版本 6.2.1,并且我观察到长度大于 100 左右的未初始化字符串被初始化为"". 读取未初始化的字符串是未定义的行为,因此编译器可以""根据需要随意将其设置为,并且当字符串足够长时,GCC 似乎会这样做。当然,我永远不会在生产代码中依赖这种行为 - 我只是好奇这种行为在 GCC 中来自哪里。如果它不在 GCC 代码中的某个地方,那么它不断发生就是一个非常奇怪的巧合。

\n

如果我写下面的程序

\n
/* string_initialization.c */\n#include <stdio.h>\n\nint main()\n{\n  char short_string[10];\n  char long_string[100];\n  char long_long_string[1000];\n\n  printf("%s\\n", short_string);\n  printf("%s\\n", long_string);\n  printf("%s\\n", long_long_string);\n\n  return(0);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

并用 GCC 编译并运行它,我得到:

\n
$ ./string_initialization\n\xef\xbf\xbdQE\xef\xbf\xbd\n\n\n$\n
Run Code Online (Sandbox Code Playgroud)\n

(有时第一个字符串也是空的)。这表明如果字符串足够长,GCC 会将其初始化为"",但否则并不总是这样做。

\n

如果我用 GCC 编译以下程序并运行它:

\n
#include <stdio.h>\n\nint main()\n{\n  char long_string[100];\n  int i;\n\n  for (i = 0 ; i < 100 ; ++i)\n  {\n    printf("%d ", long_string[i]);\n  }\n  printf("\\n");\n\n  return(0);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

然后我得到

\n
0 0 0 0 0 0 0 0 -1 -75 -16 0 0 0 0 0 -62 0 0 0 0 0 0 0 15 84 -42 -17 -4 127 0 0 14 84 -42 -17 -4 127 0 0 69 109 79 -50 46 127 0 0 1 0 0 0 0 0 0 0 -35 5 64 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -112 5 64 0 0 0 0 0 80 4 64 0 0 0 0 0 16 85 -42 -17 \n
Run Code Online (Sandbox Code Playgroud)\n

所以只是字符串的开头被初始化为0,而不是整个字符串。

\n

我想查看 GCC 源代码以了解策略是什么,但我不太了解该代码库,无法知道在哪里查找。

\n

背景:我的计算机科学学生提交了一些作品,其中他们声明字符串的长度为 1000,因为“否则会打印奇怪的符号”。你大概可以猜到原因。我希望能够给他们一个很好的答案,解释为什么会发生这种情况以及为什么他们的“修复”有效。

\n

更新:感谢那些提供有用答案的人。我刚刚发现,如果字符串长度为 1000,我的计算机会打印出一个空字符串,但如果字符串长度为 960,则打印出垃圾。请参阅 pts 的答案以获得更好的解释。当然,这一切完全依赖于系统,并不是 GCC 的一部分。

\n

pts*_*pts 5

short_string正如其他人之前评论的那样,根据 C 标准,读取未初始化的数据(例如 的元素)是未定义的行为。

如果您对使用 GCC 编译它并在 Linux 上运行它时实际发生的情况感兴趣,这里有一些见解。

main不是程序启动时运行的第一个函数。入口点通常被调用_start,并且它调用main. 运行时这些未初始化数组中堆栈上的内容main取决于之前放置的内容,即_start调用之前所做的操作main。做什么_start取决于 GCC 和 libc。

要弄清楚实际发生的情况,您可能需要使用 编译程序gcc -static -g,然后在调试器中运行它,如下所示:

$ gcc -static -g -o myprog myprog.c
$ gdb ./myprog
(gdb) b _start
(gdb) run
(gdb) s
Run Code Online (Sandbox Code Playgroud)

相反,s您可能想发出其他 GDB 命令来反汇编_start,并逐条指令地运行它。

为什么你的程序从未初始化的长数组中读取0的数据比从未初始化的短数组中读取的数据多的一个可能的解释是,堆栈在开始_start运行之前(大部分)都是全0,然后_start覆盖了堆栈的一些字节,但长数组的开头位于堆栈中尚未被 覆盖的部分_start,因此它仍然是全部0s。使用调试器来确认。

您可能还对从未初始化的全局数组中读取数据感兴趣。这些数组保证由 C 标准初始化0,这是由 GCC 将它们放入.bss节中实现的。请参阅有关 .bss 部分未零初始化的信息,了解如何.bss初始化。