GCC为什么以及如何使用缺少的return语句编译函数?

时间:2011-09-02 08:22:51

标签: c linux gcc

#include <stdio.h>

char toUpper(char);

int main(void)
{
    char ch, ch2;
    printf("lowercase input : ");
    ch = getchar();
    ch2 = toUpper(ch);
    printf("%c ==> %c\n", ch, ch2);

    return 0;
}

char toUpper(char c)
{
    if(c>='a'&&c<='z')
        c = c - 32;
}

在toUpper函数中,返回类型为char,但toUpper()中没有“return”。并使用gcc(GCC)4.5.1 20100924(Red Hat 4.5.1-4),fedora-14编译源代码。

当然,会发出警告:“警告:控制到达无效功能的结束”,但是,运行良好。

使用gcc编译时代码中发生了什么? 在这种情况下,我想得到一个可靠的答案。 谢谢:))

8 个答案:

答案 0 :(得分:19)

你发生的事情是,当C程序被编译成汇编语言时,你的toUpper函数最终会像这样结束,或许:

_toUpper:
LFB4:
        pushq   %rbp
LCFI3:
        movq    %rsp, %rbp
LCFI4:
        movb    %dil, -4(%rbp)
        cmpb    $96, -4(%rbp)
        jle     L8
        cmpb    $122, -4(%rbp)
        jg      L8
        movzbl  -4(%rbp), %eax
        subl    $32, %eax
        movb    %al, -4(%rbp)
L8:
        leave
        ret

32减法在%eax寄存器中进行。并且在x86调用约定中,这是预期返回值的寄存器!所以......你很幸运。

但请注意警告。他们是有原因的!

答案 1 :(得分:7)

它取决于Application Binary Interface以及哪些寄存器用于计算。

E.g。在x86上,第一个函数参数和返回值存储在EAX中,因此gcc最有可能使用它来存储计算结果。

答案 2 :(得分:2)

基本上,c被推入到后来应该用返回值填充的位置;因为它不会被return覆盖,所以它会以返回的值结束。

请注意,依赖于此(使用C或任何其他语言,这不是一个明确的语言功能,如Perl),是一个坏主意™。在极端。

答案 3 :(得分:2)

重要的是要理解的一件事是,省略return语句很少是可诊断的错误。考虑这个功能:

int f(int x)
{
    if (x!=42) return x*x;
}

只要您从不使用42的参数调用它,包含此函数的程序是完全有效的C并且不会调用任何未定义的行为,尽管如果您 调用UB调用f(42),然后尝试使用返回值。

因此,虽然编译器可以为缺少的返回语句提供警告启发式,但是如果没有误报或漏报,则不可能这样做。这是解决停止问题不可能的结果。

答案 4 :(得分:1)

我无法告诉你平台的细节,因为我不知道,但对你所看到的行为有一个通用的答案。

当编译返回某个函数时,编译器将使用有关如何返回该数据的约定。它可以是机器寄存器,也可以是定义的存储器位置,例如通过堆栈或其他任何东西(尽管通常使用机器寄存器)。编译代码也可以在执行函数工作时使用该位置(寄存器或其他方式)。

如果函数没有返回任何内容,则编译器将不会生成使用返回值显式填充该位置的代码。但是,正如我上面所说,它可能在功能期间使用该位置。当您编写读取返回值(ch2 = toUpper(ch);)的代码时,编译器将编写使用其约定的代码,以检索从常规位置返回的内容。就调用者代码而言,它只会从该位置读取该值,即使没有明确写入该值。因此,你得到一个价值。

现在看看@Ray的例子,编译器使用EAX寄存器来存储上壳操作的结果。它恰好发生,这可能是返回值写入的位置。在调用端,ch2加载了EAX中的值 - 因此是幻像返回。这仅适用于x86系列处理器,与其他架构一样,编译器可能会使用完全不同的方案来决定如何组织约定

然而,好的编译器会根据本地条件,代码知识,规则和启发式方法尝试优化。所以需要注意的一点是,这只是运气而已。编译器可以优化而不是执行此操作或其他任何操作 - 您不应该回复该行为。

答案 5 :(得分:0)

没有局部变量,因此函数末尾堆栈顶部的值将是参数c。退出时堆栈顶部的值是返回值。所以c无论如何,这都是回报值。

答案 6 :(得分:0)

您应该记住,此类代码可能会因编译器而崩溃。例如,clang在此类函数结束时生成ud2指令,您的应用程序将在运行时崩溃。

答案 7 :(得分:0)

我尝试过一个小程序:

#include <stdio.h>
int f1() {
}
int main() {
    printf("TEST: <%d>\n",  f1());
    printf("TEST: <%d>\n",  f1());
    printf("TEST: <%d>\n",  f1());
    printf("TEST: <%d>\n",  f1());
    printf("TEST: <%d>\n",  f1());
}

结果:

TEST:&lt; 1&gt;

TEST:&lt; 10&gt;

TEST:&lt; 11&gt;

TEST:&lt; 11&gt;

TEST:&lt; 11&gt;

我使用过mingw32-gcc编译器,因此可能存在差异。

你可以试玩,例如尝试char函数。 只要你不使用结果值,它就可以正常工作。

#include <stdio.h>
char f1() {
}
int main() {
    f1();
}

但我仍然建议设置void函数或给出一些返回值。

你的功能似乎需要回报:

char toUpper(char c)
{
    if(c>='a'&&c<='z')
        c = c - 32;
    return c;
}