请考虑以下代码,以生成一个从0到9的数字列表,以及将值2和-3提升到列表中相应数字的幂的函数:
#include <stdio.h>
int power(int m, int n);
main()
{
int i;
for (i = 0; i <= 10; ++i)
printf("%d %d %d\n", i, power(2, i), power(-3, i));
return 0;
}
int power(int base, int n)
{
int i, p;
p = 1;
for (i = 1; i <= n; ++i)
p = p * base;
// return statement purposefully omitted. //
}
当然,如果没有幂函数的return语句,程序将无法正常运行,但是通过运行编写的代码,我得到以下输出:
0 1 1
1 2 2
2 3 3
3 4 4
4 5 5
5 6 6
6 7 7
7 8 8
8 9 9
9 10 10
我想知道输出的第二和第三列中的数字是哪里来的?在没有有效的功率返回值的情况下,控件会转回到调用函数,但是为什么要输出这些数字呢?
答案 0 :(得分:0)
如@dyukha和@Daniel H所指出的,返回的值是“无论您的EAX
寄存器中是什么”。您的函数在for
循环上完成,因此在返回(结束函数)之前实现的最后一条指令可能是分支测试,以检查i <= n
(循环条件)。您实际上可以通过使用编译器生成代码的汇编版本(选项EAX
)来检查将哪些变量设置为-S
寄存器。在调用
popq %rbp
retq
在功能末尾。
在我的计算机上,我尝试使用Apple LLVM版本9.0.0(clang-900.0.39.2),该版本会为我的功能生成以下内容:
movl %edi, -8(%rbp)
movl %esi, -12(%rbp)
movl $1, -20(%rbp)
movl $1, -16(%rbp)
LBB2_1: ## =>This Inner Loop Header: Depth=1
movl -12(%rbp), %eax
cmpl -16(%rbp), %eax
jl LBB2_4
## BB#2: ## in Loop: Header=BB2_1 Depth=1
movl -20(%rbp), %eax
imull -8(%rbp), %eax
movl %eax, -20(%rbp)
## BB#3: ## in Loop: Header=BB2_1 Depth=1
movl -16(%rbp), %eax
addl $1, %eax
movl %eax, -16(%rbp)
jmp LBB2_1
LBB2_4:
movl -4(%rbp), %eax
popq %rbp
retq
如您所见,我有4个引人注目的地址:-8(%rbp)
,-12(%rbp)
,-16(%rbp)
和-20(%rbp)
。给定C代码中的声明顺序和初始化顺序,-8(%rbp)
是base
,-12(%rbp)
是n
,-16(%rbp)
是{{1} },而i
是-20(%rbp)
。
p
是您的循环条件。检查说明是
*在LBB2_1
中移动n
的值
*是%eax
的值比%eax
的值小,将结果存储在i
中
*如果%eax
说它较低,请转到标签%eax
,否则继续下一条指令
BB#2之后的三个指令是您实际的乘法。 BB#3之后的三条指令是您递增LBB2_4
,然后无条件跳转到标签i
上的循环条件。
幂函数的结尾是获取存储器地址LBB2_1
中的所有内容,将其放入-4(%rbp)
中,然后离开该函数(重置堆栈指针,将值放入{{1} }到上一个堆栈帧中的适当变量。
在我的编译器生成的代码中,我看不到与您所得到的结果相同的结果,因为每次最后两列等于0(%eax
从未设置为任何值)时,都会得到该结果。除非在添加对另一个函数%eax
的调用时,以两个整数为参数,并且具有两个局部整数变量(以确保新堆栈框架的大小与幂函数的大小相同)。该函数实际上设置了地址-4(%rbp)
。在进入循环之前立即调用函数时,我实际上从函数foo
中返回的函数-4(%rbp)
中设置的-4(%rbp)
中找到了值。
正如一位同事告诉我的那样,玩未定义的行为很危险,因为允许编译器以任何喜欢的方式对其进行处理。可能是因为价值而召唤了一个恶魔。
在确定的TL; DR中,此未定义的行为由编译器以某种方式处理。是否从一个已定义的局部变量中移出某个值,或者是否有特殊的地方移到寄存器foo
中,这取决于编译器。无论如何,无论挂在哪里,都将在调用power
时返回。