Relationship between return value and garbage value

104 Views Asked by At

What happens if the return statement does not produce a return value in a function that produces a return value in C language? Does the compiler use garbage values in this case? Is this called undefined behavior?

For example;

#include <stdio.h>

int func() {

    
}

int main() {
    int result = func();
    printf("Result: %d\n", result);
    return 0;
}


Output of this code : Result: 0

I am expecting this is a undefined behavior.

3

There are 3 best solutions below

0
Eric Postpischil On

C 2018 6.9.1 12 says:

Unless otherwise specified, if the } that terminates a function is reached, and the value of the function call is used by the caller, the behavior is undefined.

It is “otherwise specified” for the main function, because there are special rules for it in C 2018 5.1.2.2.3 1. For the function func in the question, it is not otherwise specified. when func is executed, program control reaches the } that terminate func. Then, in the caller, main, the return value of func is used. Therefore, the behavior of this program is not defined by the C standard.

What happens if the return statement does not produce a return value in a function that produces a return value in C language?

If a called function and a calling function are compiled in separate source files and there is no extra communication between the object modules, such as link-time optimization information, then the compiler cannot see, when compiling the calling function, that the called function does not return a value. So the compiler must generate instructions that work if the function does return a value. In this case, the calling routine expects the function to return a value, so the instructions in it will fetch the value from the place where a value should be returned, usually a processor register designated for that purpose.

In the case in the question, with the functions in the same source file, the compiler can see there is undefined behavior. When compiling with optimization off, the compiler may act as described above. When optimization is on, a good compiler will see the code has undefined behavior. Then what happens depends on the design of the compiler. Likely behaviors include:

  1. The compiler reports the problem and stops compiling.
  2. The compiler eliminates some of the code it would otherwise generate. For example, it might remove the code that calls func but leave the printf.
  3. The compiler eliminates all of the code on the path with undefined behavior.
  4. The compiler replaces code on the path with undefined behavior with an instruction that generates a trap.

In case 3, it might take out everything in main but leave the return. That is not actually quite everything, since the return is left. I have seen a compiler actually remove everything in main, leaving just an entry point with nothing to stop the code from continuing on into whatever is linked into memory after it.

Case 2 can have some interesting effects. Without the undefined behavior, the normal sequence would be:

  1. Call func.
  2. Copy the value from the register (say eax) used to return it to the register used to pass it to printf (say esi).
  3. Call printf.

With undefined behavior in the code, the compiler might remove 1 and 2 but leave 3. In this case, the program will print the value in esi, not the value in eax. That is, it will not print the value that was in the register used to return values; it will print the value in some other register. That is important to know when debugging—one way undefined behavior may manifest is by treating things as if they have values coming from unexpected places.

4
Ayoub ASSAOUD On

this is actualy due to the complier!

lets check this code:

#include <stdio.h>

int func1(){
        return 155;
}

int func(){
        int ff = func1();
}

int main(){
        int result = func();
        printf("result %d \n", result);
        return 0;
}

try this out, it gives result 155, this is wonderful, isn't it?

what actually happens here?

Answer: when you leave a function without any return value, it means you do not change the value of the register R0 (kind of memory, a lower level language than C, called Assembly language), which is responsible of passing the return value to the caller function (this is refered as call stack) the complier checks this register to assign it to the variable in which the function was called. you have to dig more to really know what's happening when you compile your program.

I hope you this provides you a clearer understanding to what is behind undefined behavior

0
supercat On

When the C Standard was written, a guiding principle was that if there might exist some implementations where it might sometimes be difficult to handle some particular corner cases in a manner consistent with sequential program execution, those corner cases should be characterized as UB, on the assumption that compilers whose customers would want them to behave "in a documented manner charactersitic of the environment" would behave in such fashion without regard for whether or not the Standard required that they do so. Consider a function like:

uint16_t test1(uint32_t x);

void test(uint32_t x)
{
  uint16_t temp = test1(x);
  if (temp < 65536)
    test1(temp);
}

On a 32-bit platform like the ARM, the platform ABI might specify that a function declared as being of type uint16_t must always return with R0 holding a value in the range 0-65535, but if test1() falls off the end, it might simply return whatever happened to be in R0 at the time. A compiler generating code for test() might recognize that because test1() is specified to always return a value 0-65535, R0 may be copied to another 32-bit register without modification, and that value may be passed back to test unmodified even if it exceeds 65535.

Rather than try to specify what may or may not happen, the Standard waives jurisdiction on the basis that compiler writers and their customers will know more about their target platforms and application requirements than the Committee ever could.

Note that there was never any intention to invite compiler writers to use the notion that programs will never receive inputs over which the Standard waives jurisdiction as an invitation to make further inferences, e.g. treating a sequence like

x=test(y);
if (y < 65536) doSomething();

as an invitation to unconditionally call doSomething() if test(y) is known to fall off the end when y exceeds 65536, regardless of whether code actually does anything with the value stored in x.