【问题标题】:Interesting return behavior of C function [duplicate]C函数的有趣返回行为[重复]
【发布时间】:2019-03-04 19:53:07
【问题描述】:

我有一个函数,它以 char 数组的形式接受数学运算,返回一个 int 结果(这一切都有效,纯粹是为了上下文,与问题无关)。

当然,我的函数定义是:int calc(char* operation) {},它期望返回一个 int。

在解析字符串以确定操作数和要执行的操作后,我将结果分配给一个变量。我刚刚意识到我忘记将 return 语句放在函数中,但我仍然得到正确的结果......

这里是函数。我原来忘记了最后一行。

// Function to return int results of operation specified in char* (argv[1])
int calc(char* operation)
{
    int op_index = 0;
    int end_index = 0;
    for (int i = 0; i < 128; i ++)
    {
        if ((operation[i] < 48 || operation[i] > 57) && op_index == 0)
            op_index = i;
        if (operation[i] == '\0')
        {
            end_index = i;
        i = 128;
        }
    }

    int opa = 0;
    int opb = 0;
    for (int i = 0; i < op_index; i ++)
        opa += (operation[i]-48)*power(10, op_index - (i+1));
    for (int i = op_index+1; i < end_index; i ++)
        opb += (operation[i]-48)*power(10, end_index - (i+1));

    int res = 0;
    if (operation[op_index] == '+')
        res = opa + opb;
    else if (operation[op_index] == '-')
        res = opa - opb;
    else if (operation[op_index] == '*')
        res = opa * opb;
    else if (operation[op_index] == '/')
        res = opa / opb;
    else if (operation[op_index] == '%')
        res = opa % opb;

    // This is the line that I had forgotten... but still got the right results when calling this function
    return res;
}

有人对此有解释吗?我的猜测是它默认返回最后一个函数调用的结果,由于最终语句的 if/else 结构,这将是正确的。

谢谢!

【问题讨论】:

  • 在非 void 函数的某个路径上没有 return 是未定义的行为。
  • 对于未定义的行为,很难说出为什么会这样,因为它取决于您的特定编译器和机器。重要的是要理解在以后的调用中不能依赖这种行为。如果您好奇并想深入挖掘,您可能可以通过查看此函数的程序集获得一些见解。
  • 如果您不知道输入字符串的大小,最好使用(size_t n, char operation[n]) 而不是(char *operation),如果您在编译时知道它,最好使用(char (*operation)[N])。第一种方法告诉您输入缓冲区的大小。第二个更强大,因为如果输入缓冲区的大小不同,它就不会编译,因此更安全,但只有在编译时知道数组大小时才会如此。另外由于后者不是数组,而是指向数组的指针,所以用法会有所不同。
  • 一个简单的观点是调用和返回是通过将参数压入调用堆栈,然后将结果从调用堆栈中弹出来实现的。因为您在calc函数中省略了return语句,在调用函数要查找的位置的调用堆栈中没有任何内容。所以,当调用函数出栈寻找结果时,结果发现栈上的数据恰好是你声明的最后一个变量,在本例中是res。但是,它可能是其他一些变量,或者您可能会在访问不可用的信息时崩溃

标签: c return return-value return-type


【解决方案1】:

技术上未定义的行为。

如果这是 x86 Intel,则可能发生的情况是,在从函数返回之前执行的数学运算恰好将预期的返回值留在 EAX 寄存器中。对于返回整数的函数,EAX 寄存器也是返回值返回给调用者的方式。

calc 函数的尾部生成了如下所示的程序集:

    int res = 0;
 mov         dword ptr [res],0  
    if (operation[op_index] == '+')
 mov         eax,dword ptr [operation]  
 add         eax,dword ptr [op_index]    // MATH OPERATION WINDS UP IN EAX REGISTER
 movsx       ecx,byte ptr [eax]  
 cmp         ecx,2Bh  
 jne         calc+149h (05719F9h)  

然后像这样调用代码:

int x;
x = calc((char*)"4+5");
printf("%d\n", x);

生成的程序集是这样的

    x = calc((char*)"4+5");
 push        offset string "4+5" (0E87B30h)  
 call        _calc (0E8128Ah)  
 add         esp,4  
 mov         dword ptr [x],eax   // TAKE EAX AS RESULT OF FUNCTION AND ASSIGN TO X

但是当我将项目设置从调试构建切换到优化零售时,所有的赌注都没有了。编译器和链接器将开始内联汇编,进行疯狂的优化等......它甚至会围绕函数没有返回任何东西的事实进行优化......事实上,它会在@附近产生错误987654325@ 声明抱怨 x 未初始化,即使它是根据计算结果明确分配的。

所以简短的回答是你很幸运。但我想指出为什么它“恰好起作用”。

【讨论】:

  • 感谢您的详细回答。有趣的是,它适用于除 mod 之外的所有操作。
  • @SpencerB - 这是因为 mod 运算符被编译成 idiv(整数除法)指令并从 EDX 寄存器中获取余数。所以没有返回值,你得到除法运算的结果。 因此,未定义的行为
  • 这是我看到的确切结果 - 不断得到除法结果。因此,这是我发现错误的唯一原因。
  • 我真的很惊讶你没有收到关于函数缺少返回值的编译器警告。在 GCC 上,您可以使用 -Wall -Wuninitialized 作为编译器选项。我建议的另一件事是,将 C 代码编译为 C++ 有时会带来更好的类型检查和警告。
【解决方案2】:

除了main 函数外,任何定义为返回值的函数都必须这样做。如果没有,并且调用函数尝试使用返回值,则您调用了undefined behavior

这是在C standard 的第 6.9.1p12 节中指定的:

如果到达终止函数的},并且 函数调用被调用者使用,行为未定义。

在这种情况下,您“幸运”地认为程序恰好可以运行,但不能保证总是如此。对您的程序进行看似无关的更改可能会改变未定义行为的表现方式。

【讨论】:

  • 幸运还是不幸?这是值得商榷的:)
  • @machine_1 这就是为什么我选择了“幸运”而不是幸运。
  • 啊,是的,引号;对。
  • @machine_1 是的,我会说不走运:) 有趣的是,我将它用作客户端服务器 RPC 交换的一部分,它一直给我正确的答案,直到我尝试 % 操作。 “未定义的行为”我想......
猜你喜欢
  • 2022-01-16
  • 1970-01-01
  • 2015-07-27
  • 1970-01-01
  • 2019-06-13
  • 2020-03-15
  • 2013-06-14
  • 1970-01-01
  • 2014-03-07
相关资源
最近更新 更多