0

I think I have found a problem with the way functions are handled by the gcc compiler.

I don't know if it's a mistake or a never distraction on something I've let slip over the years. In practice, by declaring a function and defining the latter having a return value, the compiler stores the value of the first variable allocated in the range of the function in the EAX register, and then stores it, in turn, within a variable. Example:

#include<stdio.h>

int add(int a, int b)
{
    int c = a + b;

    ;there isn't return
}

int main(void)
{
    int res = add(3, 2);

    return 0;
}

This is the output:

5

This is the x86-64 Assembly with intel syntax:

Function add:

push   rbp
mov    rbp, rsp
mov    DWORD PTR[rbp-0x14], edi  ;store first
mov    DWORD PTR[rbp-0x18], esi  ;store second
mov    edx, DWORD PTR[rbp-0x14]
mov    eax, DWORD PTR[rbp-0x18]
add    eax, esx
mov    DWORD PTR[rbp-0x4], eax
nop
pop    rbp
ret

Function main:

push   rbp
mov    rbp, rsp
sub    rsp, 0x10
mov    esi, 0x2    ;first parameter
mov    edi, 0x3    ;second parameter
call   0x1129 <add>

;WHAT??? eax = a + b, why store it?
mov    DWORD PTR[rbp-0x4], eax 
mov    eax, 0x0
leave
ret

As you can see, it saves me the sum of the parameters a and b in the variable c, but then it saves me in the variable res the eax register containing their sum, as if a function returned values.

Is this done because the function was defined with a return value?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
mattstack
  • 103
  • 6
  • What do you expect to happen instead? – fuz Apr 06 '21 at 20:32
  • since the function does not return a value, so I omitted the "return" I expect the content of the variable "c" to be lost and therefore in nesusno mod the content of eax (eax = a + b) is stored in "res ". I repeat I omitted the "return", neinte "return c" or "return a + b" – mattstack Apr 06 '21 at 20:37
  • Ok, now i see, thank you – mattstack Apr 06 '21 at 20:37
  • 1
    Make sure to always use `-Wall` and you'll get messages from the compiler for this:"control reaches end of non-void function". I think the only reason that this is a warning instead of an error is that the standard either doesn't want to force compilers to do the required analysis to detect this, or, maybe doesn't want to specify the actual analysis that is required. – Erik Eidt Apr 06 '21 at 23:04
  • 1
    @ErikEidt: In C, the behaviour is well-defined as long as the caller doesn't *use* the return value. This is for backwards compat with pre-ANSI C from before `void` and prototypes existed, so there was existing code that fell off the end of non-void functions. Even for C99/C11 haven't outlawed it. In ISO C++, it *is* undefined behaviour on the spot for execution to fall off the end of a non-void function, so `g++` will warn even without -Wall, and omit code-gen for that path of execution (not even a `ret`, just literally fall off the end in asm!) https://godbolt.org/z/e54qnKr7q – Peter Cordes Apr 07 '21 at 03:24

2 Answers2

6

What you've done is trigger undefined behavior by failing to return a value from a function and then attempting to use that return value.

This is documented in section 6.9.1p12 of the C standard:

If the } that terminates a function is reached, and the value of the function call is used by the caller, the behavior is undefined.

One of the ways that undefined behavior can manifest itself is by the program appearing to work properly, as you've seen. However, there's no guarantee that it will continue to work if for example, you added some unrelated code or compiled with different optimization settings.

dbush
  • 205,898
  • 23
  • 218
  • 273
  • Indeed this *only* works mostly-reliably with `gcc -O0` (where it is in fact fairly consistent, and abused by code-golf C answers. [Return value in unused parameter](https://stackoverflow.com/q/57437831)). It doesn't normally work with other compilers, and definitely not with optimization enabled. – Peter Cordes Apr 06 '21 at 21:03
  • It most likely works with other compilers as well, but certainly not with optimizations.Since there is no change in observable behavior, the code may be not included at all when optimizing. – Devolus Apr 06 '21 at 21:09
  • With a clever compiler, the code would probably compile to nothing but the startup code with optimization, because this specific example wouldn't do anything at all. – tofro Apr 06 '21 at 21:56
  • @PeterCordes: It used to be commonplace for low-level compilers to specify that if a function call isn't followed by any other computations, its return value will be placed in certain registers (depending upon type), and that falling off the end of a function would return to the caller whatever values were in appropriate registers, but any computations may affect those registers in unspecified ways. Such specifications, taken cumulatively, would define behavior the function return as having no weird side effects beyond returning a meaningless value. – supercat Apr 07 '21 at 15:34
2

eax is the register used to return a value, in this case because it is supposed to return int. So the caller gets whatever happens to be in that registers. However you should have gotten at least a warning, that there is no return statement.

Because your function is pretty small and the compiler decided to use the eax register for it's calculation, it appears to work.

If you switch on optimization or provide a more complex function, the result will be quite different.

Devolus
  • 21,661
  • 13
  • 66
  • 113