当我运行以下程序时,它 始终 打印 "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;
}
显然发生了什么是。
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
(或其中一部分,如 eax
或 al
)用于WIN64 ABI中整数类型(如booleans)的返回值,所以这是有意义的。这意味着 i
碰巧被用作返回值。
((bool(*)())func)()
这个表达式取一个指针到 func
,将指针投向不同类型的函数,然后调用它。 通过函数签名与原函数不匹配的指针到函数来调用一个函数是 未定义行为 这意味着任何事情都可能发生。 从这个函数调用发生的那一刻起,程序的行为就不能被推理出来。 你无法确切地预测会发生什么。 在不同的优化级别、不同的编译器、同一编译器的不同版本,或者针对不同的架构时,行为可能是不同的。
这只是因为编译器被允许假设你是 不愿 这样做。当编译器的假设和现实发生冲突时,结果就是一个真空,编译器可以在其中插入任何它喜欢的东西。
对于你的问题 "为什么会这样?"的简单回答是,很简单:因为它可以。 但明天它可能会做别的事情。
我总是被打印出来 no
所以它一定是取决于不同的编译器,因此最好的答案是UB(未定义行为)。