使未初始化变量读取的确切结果取决于另一个函数中使用的其他常数吗?

问题描述 投票:2回答:3

当我运行以下程序时,它总是打印“是”。但是,当我将SOME_CONSTANT更改为-2时,它always打印“ no”。这是为什么?我正在使用禁用优化的Visual Studio 2019编译器。

#define SOME_CONSTANT -3

void func() {
    static int i = 2;
    int j = SOME_CONSTANT;
    i += j;
}

void main() {
    if (((bool(*)())func)()) {
        printf("yes\n");
    }
    else {
        printf("no\n");
    }
}

编辑:这是func的输出程序集(IDA Pro 7.2):

sub     rsp, 18h
mov     [rsp+18h+var_18], 0FFFFFFFEh
mov     eax, [rsp+18h+var_18]
mov     ecx, cs:i
add     ecx, eax
mov     eax, ecx
mov     cs:i, eax
add     rsp, 18h
retn

这里是main的第一部分:

sub     rsp, 628h
mov     rax, cs:__security_cookie
xor     rax, rsp
mov     [rsp+628h+var_18], rax
call    ?func@@YAXXZ    ; func(void)
test    eax, eax
jz      short loc_1400012B0

这里主要是反编译的:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // eax

  func();
  if ( v3 )
    printf("yes\n");
  else
    printf("no\n");
  return 0;
}
c++ assembly visual-c++ x86-64 undefined-behavior
3个回答
4
投票

显然发生了什么:

mov     ecx, cs:i
add     ecx, eax
mov     eax, ecx   ; <- final value of i is stored in eax
mov     cs:i, eax  ; and then also stored in i itself

可以使用不同的寄存器,它恰好以这种方式工作。关于强制选择eax的代码,没有任何内容。 mov eax, ecx确实是多余的,ecx本可以直接存储到i。但是它碰巧以这种方式工作。

并且在main中:

call    ?func@@YAXXZ    ; func(void)
test    eax, eax
jz      short loc_1400012B0

rax(或它的一部分,例如eaxal)用于WIN64 ABI中整数型类型(例如布尔值)的返回值。这意味着偶然将i的最终值用作返回值。


3
投票
((bool(*)())func)()

此表达式采用指向func的指针,将该指针强制转换为其他类型的函数,然后调用它。通过指向函数签名与原始函数不匹配的函数的指针调用函数是undefined behavior,这意味着可能发生任何事情。从发生此函数调用的那一刻起,就无法推断程序的行为。您无法肯定地预测会发生什么。在不同的优化级别,不同的编译器,同一编译器的不同版本或针对不同的体系结构时,行为可能有所不同。

这仅仅是因为允许编译器假定您不会执行此操作。当编译器的假设与现实发生冲突时,结果就是真空,编译器可以在其中插入任何喜欢的东西。

对您的问题“为什么这么简单?”的简单答案很简单:因为可以。但是明天它可能会做其他事情。


1
投票

我总是打印出no,因此它必须取决于编译器,因此最佳答案是UB(未定义行为)。

© www.soinside.com 2019 - 2024. All rights reserved.