How does returning values from a function work?

后端 未结 8 1611
被撕碎了的回忆
被撕碎了的回忆 2021-01-05 12:16

I recently had a serious bug, where I forgot to return a value in a function. The problem was that even though nothing was returned it worked fine under Linux/Windows and on

相关标签:
8条回答
  • 2021-01-05 12:38

    Regarding the following statement from n3242 draft C++ Standard, paragraph 6.6.3.2, your example yields undefined behavior:

    Flowing off the end of a function is equivalent to a return with no value; this results in undefined behavior in a value-returning function.

    The best way to see what actually happens is to check the assembly code generated by the given compiler on a given architecture. For the following code:

    #pragma warning(default:4716)
    int foo(int a, int b)
    {
        int c = a + b;
    }
    
    int main()
    {
        int n = foo(1, 2);
    }
    

    ...VS2010 compiler (in Debug mode, on Intel 32-bit machine) generates the following assembly:

    #pragma warning(default:4716)
    int foo(int a, int b)
    {
    011C1490  push        ebp  
    011C1491  mov         ebp,esp  
    011C1493  sub         esp,0CCh  
    011C1499  push        ebx  
    011C149A  push        esi  
    011C149B  push        edi  
    011C149C  lea         edi,[ebp-0CCh]  
    011C14A2  mov         ecx,33h  
    011C14A7  mov         eax,0CCCCCCCCh  
    011C14AC  rep stos    dword ptr es:[edi]  
        int c = a + b;
    011C14AE  mov         eax,dword ptr [a]  
    011C14B1  add         eax,dword ptr [b]  
    011C14B4  mov         dword ptr [c],eax  
    }
    ...
    int main()
    {
    011C14D0  push        ebp  
    011C14D1  mov         ebp,esp  
    011C14D3  sub         esp,0CCh  
    011C14D9  push        ebx  
    011C14DA  push        esi  
    011C14DB  push        edi  
    011C14DC  lea         edi,[ebp-0CCh]  
    011C14E2  mov         ecx,33h  
    011C14E7  mov         eax,0CCCCCCCCh  
    011C14EC  rep stos    dword ptr es:[edi]  
        int n = foo(1, 2);
    011C14EE  push        2  
    011C14F0  push        1  
    011C14F2  call        foo (11C1122h)  
    011C14F7  add         esp,8  
    011C14FA  mov         dword ptr [n],eax  
    }
    

    The result of addition operation in foo() is stored in eax register (accumulator) and its content is used as a return value of the function, moved to variable n.

    eax is used to store a return value (pointer) in the following example as well:

    #pragma warning(default:4716)
    int* foo(int a)
    {
        int* p = new int(a);
    }
    
    int main()
    {
        int* pn = foo(1);
    
        if(pn)
        {
            int n = *pn;
            delete pn;
        }
    }
    

    Assembly code:

    #pragma warning(default:4716)
    int* foo(int a)
    {
    000C1520  push        ebp  
    000C1521  mov         ebp,esp  
    000C1523  sub         esp,0DCh  
    000C1529  push        ebx  
    000C152A  push        esi  
    000C152B  push        edi  
    000C152C  lea         edi,[ebp-0DCh]  
    000C1532  mov         ecx,37h  
    000C1537  mov         eax,0CCCCCCCCh  
    000C153C  rep stos    dword ptr es:[edi]  
        int* p = new int(a);
    000C153E  push        4  
    000C1540  call        operator new (0C1253h)  
    000C1545  add         esp,4  
    000C1548  mov         dword ptr [ebp-0D4h],eax  
    000C154E  cmp         dword ptr [ebp-0D4h],0  
    000C1555  je          foo+50h (0C1570h)  
    000C1557  mov         eax,dword ptr [ebp-0D4h]  
    000C155D  mov         ecx,dword ptr [a]  
    000C1560  mov         dword ptr [eax],ecx  
    000C1562  mov         edx,dword ptr [ebp-0D4h]  
    000C1568  mov         dword ptr [ebp-0DCh],edx  
    000C156E  jmp         foo+5Ah (0C157Ah)  
    std::operator<<<std::char_traits<char> >:
    000C1570  mov         dword ptr [ebp-0DCh],0  
    000C157A  mov         eax,dword ptr [ebp-0DCh]  
    000C1580  mov         dword ptr [p],eax  
    }
    ...
    int main()
    {
    000C1610  push        ebp  
    000C1611  mov         ebp,esp  
    000C1613  sub         esp,0E4h  
    000C1619  push        ebx  
    000C161A  push        esi  
    000C161B  push        edi  
    000C161C  lea         edi,[ebp-0E4h]  
    000C1622  mov         ecx,39h  
    000C1627  mov         eax,0CCCCCCCCh  
    000C162C  rep stos    dword ptr es:[edi]  
        int* pn = foo(1);
    000C162E  push        1  
    000C1630  call        foo (0C124Eh)  
    000C1635  add         esp,4  
    000C1638  mov         dword ptr [pn],eax  
    
        if(pn)
    000C163B  cmp         dword ptr [pn],0  
    000C163F  je          main+51h (0C1661h)  
        {
            int n = *pn;
    000C1641  mov         eax,dword ptr [pn]  
    000C1644  mov         ecx,dword ptr [eax]  
    000C1646  mov         dword ptr [n],ecx  
            delete pn;
    000C1649  mov         eax,dword ptr [pn]  
    000C164C  mov         dword ptr [ebp-0E0h],eax  
    000C1652  mov         ecx,dword ptr [ebp-0E0h]  
    000C1658  push        ecx  
    000C1659  call        operator delete (0C1249h)  
    000C165E  add         esp,4  
        }
    }
    

    VS2010 compiler issues warning 4716 in both examples. By default this warning is promoted to an error.

    0 讨论(0)
  • 2021-01-05 12:44

    As Kerrek SB mentioned, your code has ventured into the realm of undefined behavior.

    Basically, your code is going to compile down to assembly. In assembly, there's no concept of a function requiring a return type, there's just an expectation. I'm the most comfortable with MIPS, so I shall use MIPS to illustrate.

    Assume you have the following code:

    int add(x, y)
    {
        return x + y;
    }
    

    This is going to be translated to something like:

    add:
        add $v0, $a0, $a1 #add $a0 and $a1 and store it in $v0
        jr $ra #jump back to where ever this code was jumped to from
    

    To add 5 and 4, the code would be called something like:

    addi $a0, $0, 5 # 5 is the first param
    addi $a1, $0, 4 # 4 is the second param
    jal add
    # $v0 now contains 9
    

    Note that unlike C, there's no explicit requirement that $v0 contain the return value, just an expectation. So, what happens if you don't actually push anything into $v0? Well, $v0 always has some value, so the value will be whatever it last was.

    Note: This post makes some simplifications. Also, you're computer is likely not running MIPS... But hopefully the example holds, and if you learned assembly at a university, MIPS might be what you know anyway.

    0 讨论(0)
提交回复
热议问题