使用指定的初始值设定项初始化数组时的奇怪值

当我初始化数组时,除了values[3]之外,所有输出看起来都没问题。 由于某种原因, values[3]初始化为values[0]+values[5]输出非常大的数字。 我的猜测是,我试图在将values[0]+values[5]正确存储到内存之前进行分配,但如果有人能够解释这将是很好的。

 int main (void) { int values[10] = { [0]=197,[2]=-100,[5]=350, [3]=values[0] + values[5], [9]= values[5]/10 }; int index; for (index=0; index<10; index++) printf("values[%i] = %i\n", index, values[index]); return 0; } 

输出如下:

 values[0] = 197 values[1] = 0 values[2] = -100 values[3] = -1217411959 values[4] = 0 values[5] = 350 values[6] = 0 values[7] = 0 values[8] = 0 values[9] = 35 

看起来你在这里受到了未指明的行为,因为初始化列表表达式的评估顺序是未指定的,来自草案C99标准部分6.7.8

未指定初始化列表表达式中出现任何副作用的顺序。 133)

并注释133说:

特别是,评估顺序不必与子对象初始化的顺序相同。

据我所知,备注133的规范性文本将来自第6.5节:

除非后面另有说明,否则子表达式的评估顺序和副作用发生的顺序都是未指定的。

我们可以看到一个初始化器6.8一个完整表达式( 强调我的 ):

完整表达式是不是另一个表达式或声明符的一部分的表达式。 以下每个都是一个完整的表达式:初始化器 ; […]

回顾一下我的旧C ++答案 ,其中涵盖了初始化程序中的序列点,并将完整表达式置于不同的位置,然后我最初得出结论,我意识到6.7.8中的语法包含两次初始化程序

 initializer: assignment-expression { initializer-list } { initializer-list , } initializer-list: designationopt initializer initializer-list , designationopt initializer 

我最初没有注意到这一点,并认为全表达式的语句应用于上述语法中的top元素。

我现在相信像C ++一样, full-expression适用于初始化列表中的每个初始化 程序,这使我之前的分析不正确。

缺陷报告439证实了我怀疑这确实是这种情况,它包含以下示例:

 #include  #define ONE_INIT '0' + i++ % 3 #define INITIALIZERS [2] = ONE_INIT, [1] = ONE_INIT, [0] = ONE_INIT int main() { int i = 0; char x[4] = { INITIALIZERS }; // case 1 puts(x); puts((char [4]){ INITIALIZERS }); // case 2 puts((char [4]){ INITIALIZERS } + i % 2); // case 3 } 

它说:

在每次使用INITIALIZERS宏时,变量i增加三次。 在情况1和2中,没有未定义的行为,因为增量是在相对于彼此不确定地排序的表达式中,而不是未经测序的。

所以INITIALIZERS中的每个初始化器都是一个完整的表达式

由于此缺陷报告针对C11,因此值得注意的是C11在此问题的规范性文本中比C99更详细,它说:

初始化列表表达式的评估相对于彼此不确定地排序,因此未指定出现任何副作用的顺序。 152)

在将values中的各个元素分配给以下表达式之前评估以下表达式时,存在未定义的行为:

  values[0] + values[5] 

要么:

  values[5]/10 

这是未定义的行为,因为使用不确定的值会调用未定义的行为 。

在这种特定情况下,最简单的解决方法是手动执行计算:

 int values[10] = { [0]=197,[2]=-100,[5]=350, [3]= 197 + 350, [9]= 350/10 }; 

还有其他替代方法,例如在初始化后对元素39进行分配。

这与指定的初始化器无关。 尝试这样的事情时,你会得到同样的错误:

 int array[10] = {5, array[0]}; 

执行初始化列表表达式的顺序只是未指定的行为。 这意味着它是特定于编译器的,没有文档的,永远不应该依赖:

C11 6.7.9 / 23

初始化列表表达式的评估相对于彼此不确定地排序,因此未指定出现任何副作用的顺序。

由于您使用数组项初始化其他数组成员,这意味着您必须将代码更改为运行时分配而不是初始化。

  int values[10]; values[2] = -100; values[5] = 350; values[3] = values[0] + values[5]; ... 

作为副作用,您的程序现在也将更具可读性。

这是我第一次看到以这种方式初始化的东西,但我认为你看到的行为与访问尚未初始化的数组有关。 所以我在32位Ubuntu 12.04系统上使用GCC 4.6.3构建它。 在我的环境中,我得到的结果与你不同。

 gcc file.c -o file ./file values[0] = 197 values[1] = 0 values[2] = -100 values[3] = 197 values[4] = 0 values[5] = 350 values[6] = 0 values[7] = 0 values[8] = 0 values[9] = 35 objdump -d file > file.asm cat file.asm (relevant portion posted below) 080483e4 
: 80483e4: 55 push %ebp 80483e5: 89 e5 mov %esp,%ebp 80483e7: 57 push %edi 80483e8: 53 push %ebx 80483e9: 83 e4 f0 and $0xfffffff0,%esp 80483ec: 83 ec 40 sub $0x40,%esp 80483ef: 8d 5c 24 14 lea 0x14(%esp),%ebx 80483f3: b8 00 00 00 00 mov $0x0,%eax 80483f8: ba 0a 00 00 00 mov $0xa,%edx 80483fd: 89 df mov %ebx,%edi 80483ff: 89 d1 mov %edx,%ecx 8048401: f3 ab rep stos %eax,%es:(%edi) <===== 8048403: c7 44 24 14 c5 00 00 movl $0xc5,0x14(%esp) 804840a: 00 804840b: c7 44 24 1c 9c ff ff movl $0xffffff9c,0x1c(%esp) 8048412: ff 8048413: 8b 54 24 14 mov 0x14(%esp),%edx 8048417: 8b 44 24 28 mov 0x28(%esp),%eax 804841b: 01 d0 add %edx,%eax 804841d: 89 44 24 20 mov %eax,0x20(%esp) 8048421: c7 44 24 28 5e 01 00 movl $0x15e,0x28(%esp) 8048428: 00 8048429: 8b 4c 24 28 mov 0x28(%esp),%ecx 804842d: ba 67 66 66 66 mov $0x66666667,%edx 8048432: 89 c8 mov %ecx,%eax 8048434: f7 ea imul %edx 8048436: c1 fa 02 sar $0x2,%edx 8048439: 89 c8 mov %ecx,%eax 804843b: c1 f8 1f sar $0x1f,%eax

我已经在上面的输出中确定了一个关键线,我认为这标志着你生成的和我生成的内容之间的区别(标有<======)。 在使用您指定的值初始化特定数组元素之前,我正在将数组的内容归零。 在此之后发生数组元素的特定初始化。

鉴于上述行为,我不认为在初始化数组的特定元素之前假设你的数组内容没有归零是不合理的。 至于为什么行为上的差异? 我只能推测; 但我的第一个猜测是我们使用两种不同的编译器版本。

希望这可以帮助。

 int values[10] = { [0]=197,[2]=-100,[5]=350, [3]=values[0] + values[5], [9]= values[5]/10 }; 

编辑:

ISO C99标准的第6.7.8节(初始化)规定了

初始化应在初始化程序列表顺序中进行 ,每个初始化程序为特定子对象提供,覆盖同一子对象的任何先前列出的初始化程序; 132)未明确初始化的所有子对象应与具有静态存储持续时间的对象隐式初始化

但正如Shafik指出的那样, 评估顺序不必与初始化顺序相匹配

这意味着values[0] + values[5]可以从以下位置读取垃圾值:

  • values[0]
  • values[5]这是你的情况
  • 没有一个

试试这段代码:

 int values[10]; values[0]=197; values[2]=-100; values[5]=350; values[3]=values[0]+values[5]; values[9]=values[5]/10; 

然后你就像你已经完成的那样打印arrays。