当我使用 void 函数的返回值(通过转换函数指针)时究竟会发生什么?

3 c++ assembly x86-64 undefined-behavior visual-c++

当我运行以下程序时,它总是打印“是”。但是,当我更改SOME_CONSTANT-2它时,它总是打印“否”。这是为什么?我正在使用禁用优化的 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");
    }
}
Run Code Online (Sandbox Code Playgroud)

编辑:这是funcIDA 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
Run Code Online (Sandbox Code Playgroud)

这是的第一部分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
Run Code Online (Sandbox Code Playgroud)

这是主要的反编译:

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;
}
Run Code Online (Sandbox Code Playgroud)

cdh*_*wie 7

((bool(*)())func)()
Run Code Online (Sandbox Code Playgroud)

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

这仅仅是因为允许编译器假设您会这样做。当编译器的假设和现实发生冲突时,结果是一个真空,编译器可以插入任何它喜欢的东西。

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


har*_*old 5

显然发生的事情是:

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
Run Code Online (Sandbox Code Playgroud)

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

并在main

call    ?func@@YAXXZ    ; func(void)
test    eax, eax
jz      short loc_1400012B0
Run Code Online (Sandbox Code Playgroud)

rax(或其一部分,如eaxal)用于 WIN64 ABI 中整数类型(例如布尔值)的返回值,因此这是有意义的。这意味着 的最终值i碰巧被用作返回值。