关于在函数声明中省略return语句的问题

时间:2019-01-23 10:01:36

标签: c

请考虑以下代码,以生成一个从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

我想知道输出的第二和第三列中的数字是哪里来的?在没有有效的功率返回值的情况下,控件会转回到调用函数,但是为什么要输出这些数字呢?

1 个答案:

答案 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时返回。