gcc 是否将长字符串初始化为“”而不是短字符串?
Does gcc initialize long strings to `""` but not short ones?
注意: 我知道读取未初始化的字符串是未定义的行为。这个问题专门针对 GCC 实现。
我正在使用 GCC 版本 6.2.1,我发现长度大于 100 左右的未初始化字符串被初始化为 ""
。读取未初始化的字符串是未定义的行为,因此编译器可以根据需要自由将其设置为 ""
,并且当字符串足够长时,GCC 似乎正在这样做。当然,我永远不会在生产代码中依赖这种行为——我只是好奇这种行为在 GCC 中的来源。如果它不在某个地方的 GCC 代码中,那么它一直在发生是一个非常奇怪的巧合。
如果我编写如下程序
/* string_initialization.c */
#include <stdio.h>
int main()
{
char short_string[10];
char long_string[100];
char long_long_string[1000];
printf("%s\n", short_string);
printf("%s\n", long_string);
printf("%s\n", long_long_string);
return(0);
}
并用 GCC 编译和 运行 它,我得到:
$ ./string_initialization
�QE�
$
(有时第一个字符串也是空的)。这表明如果一个字符串足够长,那么 GCC 会将其初始化为 ""
,否则它不会总是这样做。
如果我用 GCC 和 运行 编译以下程序:
#include <stdio.h>
int main()
{
char long_string[100];
int i;
for (i = 0 ; i < 100 ; ++i)
{
printf("%d ", long_string[i]);
}
printf("\n");
return(0);
}
然后我得到
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
所以只是字符串的开头被初始化为 0
,而不是整个字符串。
我想查看 GCC 源代码以了解该政策是什么,但我对该代码库的了解还不够深,不知道该去哪里查看。
背景:我的 CS 学生提交了一些作业,其中他们声明一个字符串的长度为 1000,因为“否则会打印出奇怪的符号”。你大概能猜到原因。我希望能够给他们一个很好的答案,说明为什么会这样,以及为什么他们的“修复”有效。
更新: 感谢那些提供有用答案的人。我刚刚发现,如果字符串的长度为 1000,我的计算机会打印出一个空字符串,但如果字符串的长度为 960,则会打印出垃圾。请参阅 pts 的回答以获得很好的解释。当然,这一切完全取决于系统,不属于GCC。
答案很简单,发生这种情况的原因是由于读取未初始化变量的值导致的未定义行为
GCC 根本不会初始化这些字符串。您只是看到堆栈恰好包含零,并且您认为这是编译器的一些有意行为。不是。
将您的结果与 http://coliru.stacked-crooked.com/a/38f3e70be871af61 进行比较,结果表明即使第一次调用函数时数组的前几个字节恰好为零,第二次调用时字节也不为零(因为我做了堆栈变脏,编译器不初始化数组)。
您不能假设某些未定义的行为是可靠的、可重复的或故意的。这是一个非常危险的假设。
正如其他人之前评论的那样,根据 C 标准,读取未初始化的数据(例如 short_string
的元素)是未定义的行为。
如果您对通过 GCC 编译它并 运行在 Linux 上编译它时实际发生的事情感兴趣,这里有一些见解。
main
不是第一个在程序启动时获得 运行 的函数。入口点通常称为_start
,它称为main
。当 main
为 运行ning 时,这些未初始化数组中的堆栈中的内容取决于之前放在那里的内容,即 _start
在调用 main
之前所做的事情。 _start
的作用取决于 GCC 和 libc。
要弄清楚实际发生了什么,您可能需要使用 gcc -static -g
编译您的程序,并在调试器中 运行 编译程序,如下所示:
$ gcc -static -g -o myprog myprog.c
$ gdb ./myprog
(gdb) b _start
(gdb) run
(gdb) s
而不是 s
你可能想要发出其他 GDB 命令来获取 _start
的反汇编,并且 运行 它逐条指令。
为什么您的程序从未初始化的长数组读取的 0
比未初始化的短数组多,一个可能的解释可能是堆栈在开始时(大部分)都是 0,在 _start
开始 运行ning,然后 _start
覆盖了堆栈的一些字节,但是长数组的开头是堆栈中尚未被 _start
覆盖的部分,所以它仍然是所有 0
s。使用调试器确认。
您可能还对从未初始化的全局数组中读取数据感兴趣。这些数组保证被 C 标准初始化为 0
,这是由 GCC 将它们放入 .bss
部分来实现的。请参阅 how about .bss section not zero initialized 关于 .bss
的初始化方式。
注意: 我知道读取未初始化的字符串是未定义的行为。这个问题专门针对 GCC 实现。
我正在使用 GCC 版本 6.2.1,我发现长度大于 100 左右的未初始化字符串被初始化为 ""
。读取未初始化的字符串是未定义的行为,因此编译器可以根据需要自由将其设置为 ""
,并且当字符串足够长时,GCC 似乎正在这样做。当然,我永远不会在生产代码中依赖这种行为——我只是好奇这种行为在 GCC 中的来源。如果它不在某个地方的 GCC 代码中,那么它一直在发生是一个非常奇怪的巧合。
如果我编写如下程序
/* string_initialization.c */
#include <stdio.h>
int main()
{
char short_string[10];
char long_string[100];
char long_long_string[1000];
printf("%s\n", short_string);
printf("%s\n", long_string);
printf("%s\n", long_long_string);
return(0);
}
并用 GCC 编译和 运行 它,我得到:
$ ./string_initialization
�QE�
$
(有时第一个字符串也是空的)。这表明如果一个字符串足够长,那么 GCC 会将其初始化为 ""
,否则它不会总是这样做。
如果我用 GCC 和 运行 编译以下程序:
#include <stdio.h>
int main()
{
char long_string[100];
int i;
for (i = 0 ; i < 100 ; ++i)
{
printf("%d ", long_string[i]);
}
printf("\n");
return(0);
}
然后我得到
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
所以只是字符串的开头被初始化为 0
,而不是整个字符串。
我想查看 GCC 源代码以了解该政策是什么,但我对该代码库的了解还不够深,不知道该去哪里查看。
背景:我的 CS 学生提交了一些作业,其中他们声明一个字符串的长度为 1000,因为“否则会打印出奇怪的符号”。你大概能猜到原因。我希望能够给他们一个很好的答案,说明为什么会这样,以及为什么他们的“修复”有效。
更新: 感谢那些提供有用答案的人。我刚刚发现,如果字符串的长度为 1000,我的计算机会打印出一个空字符串,但如果字符串的长度为 960,则会打印出垃圾。请参阅 pts 的回答以获得很好的解释。当然,这一切完全取决于系统,不属于GCC。
答案很简单,发生这种情况的原因是由于读取未初始化变量的值导致的未定义行为
GCC 根本不会初始化这些字符串。您只是看到堆栈恰好包含零,并且您认为这是编译器的一些有意行为。不是。
将您的结果与 http://coliru.stacked-crooked.com/a/38f3e70be871af61 进行比较,结果表明即使第一次调用函数时数组的前几个字节恰好为零,第二次调用时字节也不为零(因为我做了堆栈变脏,编译器不初始化数组)。
您不能假设某些未定义的行为是可靠的、可重复的或故意的。这是一个非常危险的假设。
正如其他人之前评论的那样,根据 C 标准,读取未初始化的数据(例如 short_string
的元素)是未定义的行为。
如果您对通过 GCC 编译它并 运行在 Linux 上编译它时实际发生的事情感兴趣,这里有一些见解。
main
不是第一个在程序启动时获得 运行 的函数。入口点通常称为_start
,它称为main
。当 main
为 运行ning 时,这些未初始化数组中的堆栈中的内容取决于之前放在那里的内容,即 _start
在调用 main
之前所做的事情。 _start
的作用取决于 GCC 和 libc。
要弄清楚实际发生了什么,您可能需要使用 gcc -static -g
编译您的程序,并在调试器中 运行 编译程序,如下所示:
$ gcc -static -g -o myprog myprog.c
$ gdb ./myprog
(gdb) b _start
(gdb) run
(gdb) s
而不是 s
你可能想要发出其他 GDB 命令来获取 _start
的反汇编,并且 运行 它逐条指令。
为什么您的程序从未初始化的长数组读取的 0
比未初始化的短数组多,一个可能的解释可能是堆栈在开始时(大部分)都是 0,在 _start
开始 运行ning,然后 _start
覆盖了堆栈的一些字节,但是长数组的开头是堆栈中尚未被 _start
覆盖的部分,所以它仍然是所有 0
s。使用调试器确认。
您可能还对从未初始化的全局数组中读取数据感兴趣。这些数组保证被 C 标准初始化为 0
,这是由 GCC 将它们放入 .bss
部分来实现的。请参阅 how about .bss section not zero initialized 关于 .bss
的初始化方式。