繁体   English   中英

GCC为什么以及如何通过缺少return语句来编译函数?

[英]Why and how does GCC compile a function with a missing return statement?

#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编译期间该代码中发生了什么? 在这种情况下,我想得到一个可靠的答案。 谢谢 :)

您遇到的是,当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

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

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

它取决于应用程序二进制接口以及用于计算的寄存器。

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

本质上,将c推送到应该稍后用返回值填充的位置; 由于不会被return覆盖,因此最终会返回为值。

请注意,依赖于此(使用C语言或其他不是显式语言功能的语言,例如Perl)是Bad Idea™。 在极端。

需要了解的一件事很重要,那就是省略return语句很少是可诊断的错误。 考虑以下功能:

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

只要你永远不与42的参数调用它,包含此功能的程序是完全合法的C和,不调用任何不确定的行为,尽管事实上,它调用UB如果你叫f(42)并随后试图使用返回值。

这样,尽管编译器有可能为缺少的return语句提供警告启发式,但如果没有误报或误报,就不可能这样做。 这是不可能解决暂停问题的结果。

我不知道您平台的细节,因为我不知道,但是您所看到的行为有一个普遍的答案。

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

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

现在来看@Ray的示例,编译器使用EAX寄存器存储上套管操作的结果。 碰巧的是,这可能是返回值被写入的位置。 在调用方ch2上加载了EAX中的值-因此是幻像返回。 这仅适用于x86系列处理器,因为在其他体系结构上,编译器可能会使用完全不同的方案来决定如何组织约定。

但是,优秀的编译器将根据局部条件,代码知识,规则和启发式方法尝试进行优化。 因此要注意的重要一点是,这只是运气。 编译器可以优化而不执行此操作或执行其他操作-您不应就此行为进行回复。

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

我尝试了一个小程序:

#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());
}

结果:

测试:<1>

测试:<10>

测试:<11>

测试:<11>

测试:<11>

我使用了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;
}

没有局部变量,因此函数末尾堆栈顶部的值将是参数c。 退出时位于堆栈顶部的值是返回值。 因此,无论c保持多少,这就是返回值。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM