当大括号括起来的列表中的初始值设定项少于聚合的元素或成员时,C 编译器如何声明数组?
How does C compiler declare array when there are fewer initializers in a brace-enclosed list than there are elements or members of an aggregate?
这可能是一个简单的问题,但我试图了解编译器如何将数组中的所有元素分配给零,例如:
'''数组[5] = {0}; '''
我检查了 gcc.gnu.org 以获得一些解释,但没有成功。也许我找错了位置。我还检查了 C99 并偶然发现了第 6.7.8/21 节,但它没有告诉我它是如何初始化的。我还查看了有关此主题的其他帖子,但大多数都在讨论发生了什么,而不是如何发生。
无论如何,我的问题是:
编译器如何通过这个单一的表达式将所有元素赋零?
在哪里可以找到有关编译器如何工作的信息,特别是上面的示例?
谢谢...
这是一个实现细节,在不同的编译器和不同的源代码之间会有很大差异,但一般答案是相同的 - 编译器将生成将未初始化元素清零所需的任何代码。
以下是我的实现(gcc 版本 7.3.1)的变化情况。
源代码(文件init.c
):
#include <stdio.h>
int main( void )
{
int arr[5] = {0};
for ( size_t i = 0; i < 5; i++ )
printf( "arr[%zu] = %d\n", i, arr[i] );
return 0;
}
使用命令编译
gcc -o init -std=c11 -pedantic -Wall -Werror init.c
给我以下机器代码(使用objdump -d init
查看):
00000000004004c7 <main>:
4004c7: 55 push %rbp
4004c8: 48 89 e5 mov %rsp,%rbp
4004cb: 48 83 ec 20 sub [=12=]x20,%rsp ;; allocates space for arr and i, rounded up to multiple of 4
4004cf: 48 c7 45 e0 00 00 00 movq [=12=]x0,-0x20(%rbp) ;; zeros out a[0] and a[1]
4004d6: 00
4004d7: 48 c7 45 e8 00 00 00 movq [=12=]x0,-0x18(%rbp) ;; zeros out a[2] and a[3]
4004de: 00
4004df: c7 45 f0 00 00 00 00 movl [=12=]x0,-0x10(%rbp) ;; zeros out a[4]
4004e6: 48 c7 45 f8 00 00 00 movq [=12=]x0,-0x8(%rbp) ;; zeros out i
...
超过一定大小,一次显式清零一个或两个元素变得很麻烦 - 当我将数组大小更改为 50 时,生成的汇编代码变为
00000000004004c7 <main>:
4004c7: 55 push %rbp
4004c8: 48 89 e5 mov %rsp,%rbp
4004cb: 48 81 ec d0 00 00 00 sub [=13=]xd0,%rsp
4004d2: 48 8d 95 30 ff ff ff lea -0xd0(%rbp),%rdx
4004d9: b8 00 00 00 00 mov [=13=]x0,%eax
4004de: b9 19 00 00 00 mov [=13=]x19,%ecx
4004e3: 48 89 d7 mov %rdx,%rdi
4004e6: f3 48 ab rep stos %rax,%es:(%rdi)
4004e9: 48 c7 45 f8 00 00 00 movq [=13=]x0,-0x8(%rbp)
4004f0: 00
...
rep stos
指令本质上是一个循环(我不会流利地说 x86 汇编,其他人将不得不解释它是如何工作的)。如果我理解正确的话,它会连续将数组的每个元素清零,而不是在一条指令中将整个数组清零。
C 2018 6.7.9 19 说:
… all subobjects that are not initialized explicitly shall be initialized implicitly the same as objects that have static storage duration.
C 2018 6.7.9 10 表示,对于具有未显式初始化的静态存储持续时间的对象:
— if it has pointer type, it is initialized to a null pointer;
— if it has arithmetic type, it is initialized to (positive or unsigned) zero;
— if it is an aggregate, every member is initialized (recursively) according to these rules, and any padding is initialized to zero bits;
— if it is a union, the first named member is initialized (recursively) according to these rules, and any padding is initialized to zero bits;
因此,当您初始化数组的一部分而不是全部时,C 标准的规则要求编译器将其他元素初始化为算术类型的零和指针类型的空指针。
编译器可以根据自己的选择自由实施。该方法通常不是文档,它因情况而异:对于小数组,编译器可能会生成显式指令。对于大型数组,编译器可能会生成一个循环(包括实现循环到零内存的单个指令)或调用库例程。或者,如果编译器可以检测到初始化对于正确的程序操作实际上不是必需的,它可能会忽略它。例如,如果编译器可以看到后续赋值在使用该数组元素之前将数组元素设置为某个值,则编译器可能会忽略数组元素的任何归零,而是让赋值成为初始值。
这可能是一个简单的问题,但我试图了解编译器如何将数组中的所有元素分配给零,例如: '''数组[5] = {0}; '''
我检查了 gcc.gnu.org 以获得一些解释,但没有成功。也许我找错了位置。我还检查了 C99 并偶然发现了第 6.7.8/21 节,但它没有告诉我它是如何初始化的。我还查看了有关此主题的其他帖子,但大多数都在讨论发生了什么,而不是如何发生。
无论如何,我的问题是:
编译器如何通过这个单一的表达式将所有元素赋零?
在哪里可以找到有关编译器如何工作的信息,特别是上面的示例?
谢谢...
这是一个实现细节,在不同的编译器和不同的源代码之间会有很大差异,但一般答案是相同的 - 编译器将生成将未初始化元素清零所需的任何代码。
以下是我的实现(gcc 版本 7.3.1)的变化情况。
源代码(文件init.c
):
#include <stdio.h>
int main( void )
{
int arr[5] = {0};
for ( size_t i = 0; i < 5; i++ )
printf( "arr[%zu] = %d\n", i, arr[i] );
return 0;
}
使用命令编译
gcc -o init -std=c11 -pedantic -Wall -Werror init.c
给我以下机器代码(使用objdump -d init
查看):
00000000004004c7 <main>:
4004c7: 55 push %rbp
4004c8: 48 89 e5 mov %rsp,%rbp
4004cb: 48 83 ec 20 sub [=12=]x20,%rsp ;; allocates space for arr and i, rounded up to multiple of 4
4004cf: 48 c7 45 e0 00 00 00 movq [=12=]x0,-0x20(%rbp) ;; zeros out a[0] and a[1]
4004d6: 00
4004d7: 48 c7 45 e8 00 00 00 movq [=12=]x0,-0x18(%rbp) ;; zeros out a[2] and a[3]
4004de: 00
4004df: c7 45 f0 00 00 00 00 movl [=12=]x0,-0x10(%rbp) ;; zeros out a[4]
4004e6: 48 c7 45 f8 00 00 00 movq [=12=]x0,-0x8(%rbp) ;; zeros out i
...
超过一定大小,一次显式清零一个或两个元素变得很麻烦 - 当我将数组大小更改为 50 时,生成的汇编代码变为
00000000004004c7 <main>:
4004c7: 55 push %rbp
4004c8: 48 89 e5 mov %rsp,%rbp
4004cb: 48 81 ec d0 00 00 00 sub [=13=]xd0,%rsp
4004d2: 48 8d 95 30 ff ff ff lea -0xd0(%rbp),%rdx
4004d9: b8 00 00 00 00 mov [=13=]x0,%eax
4004de: b9 19 00 00 00 mov [=13=]x19,%ecx
4004e3: 48 89 d7 mov %rdx,%rdi
4004e6: f3 48 ab rep stos %rax,%es:(%rdi)
4004e9: 48 c7 45 f8 00 00 00 movq [=13=]x0,-0x8(%rbp)
4004f0: 00
...
rep stos
指令本质上是一个循环(我不会流利地说 x86 汇编,其他人将不得不解释它是如何工作的)。如果我理解正确的话,它会连续将数组的每个元素清零,而不是在一条指令中将整个数组清零。
C 2018 6.7.9 19 说:
… all subobjects that are not initialized explicitly shall be initialized implicitly the same as objects that have static storage duration.
C 2018 6.7.9 10 表示,对于具有未显式初始化的静态存储持续时间的对象:
— if it has pointer type, it is initialized to a null pointer;
— if it has arithmetic type, it is initialized to (positive or unsigned) zero;
— if it is an aggregate, every member is initialized (recursively) according to these rules, and any padding is initialized to zero bits;
— if it is a union, the first named member is initialized (recursively) according to these rules, and any padding is initialized to zero bits;
因此,当您初始化数组的一部分而不是全部时,C 标准的规则要求编译器将其他元素初始化为算术类型的零和指针类型的空指针。
编译器可以根据自己的选择自由实施。该方法通常不是文档,它因情况而异:对于小数组,编译器可能会生成显式指令。对于大型数组,编译器可能会生成一个循环(包括实现循环到零内存的单个指令)或调用库例程。或者,如果编译器可以检测到初始化对于正确的程序操作实际上不是必需的,它可能会忽略它。例如,如果编译器可以看到后续赋值在使用该数组元素之前将数组元素设置为某个值,则编译器可能会忽略数组元素的任何归零,而是让赋值成为初始值。