Search code examples
creturnreturn-valuereturn-type

Interesting return behavior of C function


I have a function which takes in a mathematical operation in the form of a char array, returning an int result (this all works, and is purely for context, not really related to the question).

Naturally, my function definition is: int calc(char* operation) {} which expects an int to be returned.

After parsing the string to determine the operands and the operation to be performed, I assign the result to a variable. I just realized that I had forgotten to put the return statement in the function, and yet I was still getting the correct results...

Here is the function. I had forgotten the last line originally.

// 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;
}

Does anyone have an explanation for this? My guess is that it is returning, by default, the result of the last function call, which would be correct because of the if/else structure of the final statements.

Thanks!


Solution

  • Technically undefined behavior.

    If this is x86 Intel, what is likely happening is that the math operations performed prior to returning from the function are just happening to be leaving the intended return value in the EAX register. And for functions that return integers, the EAX register is also how the return value gets passed back to the caller.

    Tail end of your calc function has generated assembly that looks like this:

        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)  
    

    And invoking code like this:

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

    Generated assembly is this

        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
    

    But the moment I switch the project settings from debug build to optimized retail, all bets are off. The compiler and linker will start inlining assembly, making crazy optimizations, etc... and it will even optimize around the fact that the function isn't returning anything... As a matter of fact, it will generate an error near the printf statement complaining that x is uninitialized even though it explicitly was assigned to from the result of calc.

    So the short answer is that you are getting lucky. But I wanted to point how why it "just happens to work".