【问题标题】:Unexpected static variable address behaviour意外的静态变量地址行为
【发布时间】:2014-01-14 21:26:57
【问题描述】:

这是 C 代码快照:

int* f(int x) {
    static int y;
    y = x * x;
    return &y;
}

float* g(float x) {
    static float y;
    y = x * x;
    return &y;
}

int main(void) {
    printf("*f(1)=%d\n", *f(1));
    printf("*f(2)=%d\n", *f(2));
    printf("*f(1) + *f(2)=%d\n", *f(1) + *f(2));
    printf("*g(1.0)=%f\n", *g(1.0));
    printf("*g(2.0)=%f\n", *g(2.0));
    printf("*g(1.0) + *g(2.0)=%f\n", *g(1.0) + *g(2.0));
    return 0;
}

输出是:

*f(1)=1
*f(2)=4
*f(1) + *f(2)=5
*g(1.0)=1.000000
*g(2.0)=4.000000
*g(1.0) + *g(2.0)=8.000000

而且我并不真正理解 f() 和 g() 的双重行为。首先,我怀疑这是编译器问题,但 BCC 或 GCC 提供相同的输出。

*f(1) + *f(2) 的输出不应该等于*g(1.0) + g(2.0) 吗? (55.088.0

【问题讨论】:

  • 这是未指定或未定义的行为;这里没有明确定义的评估顺序。
  • 我相信奥利是正确的。更明确地说,这将取决于在加法发生之前如何存储值。如果你执行*g(1.0),那么*g(2.0)在存储值之前,你会加上4.0 + 4.0 = 8.0(记住,每个指针指向同一个静态变量的值)。否则,如果您执行*g(1.0) 并将其值存储在寄存器中,然后执行*g(2.0) 并添加结果,您将得到1.0 + 4.0 = 5.0
  • related question from the C faq;基本上,如果要保证函数调用的顺序,则需要将函数调用放在单独的语句中。
  • 我认为这在 C 2011 中可能是未定义的行为,但在 C 1999 中不是。众所周知,在 C 1999 中,如果表达式同时修改并分别使用对象的值序列点。但这在这里不会发生,因为函数调用中有序列点,调用中完整表达式的结尾。因此,只是未指定调用发生的顺序。在 C 2011 中,6.5 2 表示如果副作用相对于不同的副作用或值计算未排序,则行为未定义。这似乎适用于这段代码。

标签: c variables static


【解决方案1】:

我相信奥利是正确的。更明确地说,这将取决于在加法发生之前如何存储值。如果你执行*g(1.0),那么*g(2.0)在存储值之前,你会添加4.0 + 4.0 = 8.0(记住,每个指针指向同一个静态变量的地址)。否则,如果您执行*g(1.0) 并将其值存储在寄存器中,然后执行*g(2.0) 并将结果相加,您将得到1.0 + 4.0 = 5.0

所以,实际上,这取决于编译器如何将其写入机器代码。考虑以下伪 x86 程序集(为简单起见,我们使用 int 而不是 float):

push 1 
call g ; First call to g(1);
add esp, 4 ; Pop value 1
mov ebx, eax ; Save our pointer
push 2
call g ; Call to g(2)
add esp, 4 ; Pop value 2 -- Remember that eax == ebx, now (they point to same address)
mov eax, [eax] ; Forget the pointer, store the value (4).
mov ebx, [ebx] ; Do the same thing, value is 4 since they point to same place
add eax, ebx   ; Add our two values. 4 + 4 = 8
ret

反过来,考虑以下

push 1 
call g ; First call to g(1);
add esp, 4 ; Pop value 1
mov ebx, [eax] ; Save the value at the pointer (1).
push 2
call g ; Call to g(2)
add esp, 4 ; Pop value 2 -- Remember that eax == ebx, now (they point to same address)
mov eax, [eax] ; Forget the pointer, store the value (4).
add eax, ebx   ; Add our two values. 4 + 1 = 5
ret

因此,当使用这样的共享变量而不显式存储其值时,指令的顺序真的很重要。通常,指令顺序将取决于编译器以及是否打开或关闭某些优化标志。此外,任何一个结果都可以被认为是一个合理的假设,没有硬语义来控制这一点,因为它没有真正违反标准(更多信息:请参阅下面的 Eric 回复):它是取消引用每个函数的返回值并添加结果。因此,如果编译器优化重新排序事情的完成方式,这会导致意想不到的结果。

【讨论】:

  • C 2011 中可能存在真正的违规行为。第 6.5 条第 2 段似乎说,如果对象上的不同副作用相对于彼此是无序的,并且分配给静态对象相对于彼此(对于同一对象)是无序的,因为函数调用评估的顺序未确定。
  • @EricPostpischil:感谢您指出这一点;我已经更新了我的帖子。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-07-07
  • 1970-01-01
  • 2015-10-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多