【问题标题】:Values of uninitialized array being overwritten in block scope在块范围内被覆盖的未初始化数组的值
【发布时间】:2021-06-03 09:01:43
【问题描述】:
#include <stdio.h>
#include <stdlib.h>

int main (int argc, char *argv[]) {
    int send_buf[4];
    if(1){
        int hello[5]={1,2,3,4,5};        
    }
    for(int i=0; i<4; ++i)
        printf("%d ",send_buf[i]);
    printf("\n");
    return 0;
}

输出: 1 2 3 4

在执行上述 C 代码时,输​​出始终导致分配给块范围内数组 hello 的值。但是,如果在打印数组 send_buf 之前进行了任何引用(例如打印 send_buf 的地址),则输出会导致通常的不确定值。

有人可以解释为什么send_buf 的内存已经在堆栈上分配后,send_buf 的值会被覆盖吗?

它是某种 GCC 编译器优化延迟分配一个未初始化的数组直到它被引用?

使用 GCC 版本 9.3.0 编译。

【问题讨论】:

  • 是的,这似乎是一个明显的优化。如果数组send_bufhello 从来没有同时使用过,那为什么不让它们共享同一个内存呢?
  • 这里的相关优化是live-variable analysis - 变量只需要在其实际生命周期内可用的存储空间,即在它第一次写入和最后一次读取之间。这可能是其名义生命周期的适当子集,即当它在范围内时。当然,在这种情况下,由于 send_buf 未初始化,它根本就没有被写入,所以它真的没有生命周期——你已经用你的 UB 破坏了分析,任何事情都可能发生。
  • 你的“已经被分配”可能是对正在发生的事情的一个好的心理构造,但是在“好像规则”下,编译器没有义务在声明发生,只要定义明确的代码无法区分。它只会影响您的代码,因为您的代码具有未定义的行为。
  • @NateEldredge:不幸的是,该标准没有努力区分不可移植的动作,这些动作在大多数实现中的行为至少在某种程度上是可预测的和错误的动作。未定义行为旨在邀请实现以怪异和古怪的方式行事,而不管通过定义超出标准要求的行为来扩展语言是否更有用,这一概念是 21 世纪的发明。

标签: arrays c gcc initialization compiler-optimization


【解决方案1】:

当您不在 C 中(显式或隐式)初始化对象时,C 标准不保证它们具有任何固定值。它们的值可能会受到任何其他情况的影响,甚至可能会因每次使用而有所不同。

由于您没有初始化数组,编译器可能允许任何其他操作影响它。这可能包括允许hello 的定义影响send_buf 中的值。可能起作用的一个事实是编译器会对值进行生命周期分析。例如,在如下代码中:

int p;
int q = 4;
printf("%d\n", q);
p = 3;
printf("%d\n", p);

编译器可能会看到,即使 p 定义在 q 之前,p 中也永远不会有一个“实时”值,而 q 中有一个值,因此编译器可以为q 使用与p 相同的内存。类似地,编译器可能已经决定send_buf 永远不会与hello 同时包含实时值,因此它可以为hello 使用与send_buf 相同的内存。然后,由于您从未将值放入 send_buf,因此从 hello 获取内存中的数据是您的程序的一种可能行为。

要吸取的一个教训是,在各种情况下出现在数组中的“奇怪”值是它可能没有被初始化的线索,补救办法就是初始化它。

【讨论】:

  • 就此而言,即使您在上面的示例中编写了int p = 3; int q = 4;,编译器也可以意识到直到q 死后才需要值p=3,因此它可以将其重新排序为您编写的代码,并再次让它们共享内存。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-14
相关资源
最近更新 更多