当我使用一个void函数的返回值时(通过铸造一个函数指针),到底会发生什么?

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

当我运行以下程序时,它 始终 打印 "yes"。然而,当我改变 SOME_CONSTANT-2始终 打印 "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");
    }
}

EDIT:这里是输出汇编的 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

这是main的反编译。

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
2个回答
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

不同的寄存器可以被使用, 它只是碰巧这样工作。没有任何关于代码强制 eax 要选择。那 mov eax, ecx 实在是多此一举。ecx 可以直接存储到 i. 但它恰好是这样工作的。

而在 main:

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

rax (或其中一部分,如 eaxal)用于WIN64 ABI中整数类型(如booleans)的返回值,所以这是有意义的。这意味着 i 碰巧被用作返回值。


7
投票
((bool(*)())func)()

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

这只是因为编译器被允许假设你是 不愿 这样做。当编译器的假设和现实发生冲突时,结果就是一个真空,编译器可以在其中插入任何它喜欢的东西。

对于你的问题 "为什么会这样?"的简单回答是,很简单:因为它可以。 但明天它可能会做别的事情。


3
投票

我总是被打印出来 no所以它一定是取决于不同的编译器,因此最好的答案是UB(未定义行为)。

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