'tail recursion in gcc without return statement

Apparently the following code works in GCC. I tried that code in onlinegdb.

# include <stdio.h>

int calc_gcd (int a, int b) {
    int r = a % b;
    if (r == 0) {
        return b;
    } else {
        calc_gcd (b, r);
    }
}

int main() {
    int a, b, gcd, dividend, divisor;
    printf ("Enter two numbers: ");
    scanf ("%d%d", &a, &b);
    dividend = (a > b) ? a : b;
    divisor = (a < b) ? a : b;
    gcd = calc_gcd (dividend, divisor);
    printf ("GCD = %d\n", gcd);
    return 0;
}

But it fails in clang 13 with following results

tail_recursion_gcd.c:15:1: warning: non-void function does not return a value in all control paths [-Wreturn-type]
}
^
1 warning generated.
Enter two numbers: 15 10
GCD = 127       // garbage

I'm not getting it. Clearly what GCC allows isn't intuitive, you have to return from a function.

I've tried the following but that doesn't work in gcc

# include <stdio.h>

int useless_func()
{
    3050;
}
int main() {
    printf("result = %d", useless_func());
    return 0;
}

The output is result = 0



Solution 1:[1]

TL;DR: The GCD function your teacher gave you is not portable.

What you want follows:

int calc_gcd (int a, int b) {
    int r = a % b;
    if (r == 0) {
        return b;
    } else {
      return calc_gcd (b, r);
    }
}

What version of GCC are you using, and on what operating system?

GCC is much more lenient than Clang. It looks like GCC decided that the function calc_gcd shouldn't fail and should return the result of calc_gcd(b, r).

When you compiled with Clang, I'm surprised that it didn't return 1. It did when I compiled on an M1 MacBook Pro.

From here:

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.

On x86_64 Linux, when a function that returns a non-void value is called, space is allocated on the stack for the return value. Thus, with no explicit return statement, some value is still returned.

Note: on x86_64, the return value is stored in RAX (EAX is the lower bits).

I compiled with GCC 7.5.0 on x86_64 Ubuntu Server 18.04. The command I used was gcc -o gcd gcd.c -no-pie -g. Here's the assembly dump for calc_gcd:

   0x00000000004005c7 <+0>:     push   rbp
   0x00000000004005c8 <+1>:     mov    rbp,rsp
   0x00000000004005cb <+4>:     sub    rsp,0x20
   0x00000000004005cf <+8>:     mov    DWORD PTR [rbp-0x14],edi
   0x00000000004005d2 <+11>:    mov    DWORD PTR [rbp-0x18],esi
   0x00000000004005d5 <+14>:    mov    eax,DWORD PTR [rbp-0x14]
   0x00000000004005d8 <+17>:    cdq
   0x00000000004005d9 <+18>:    idiv   DWORD PTR [rbp-0x18]
   0x00000000004005dc <+21>:    mov    DWORD PTR [rbp-0x4],edx
   0x00000000004005df <+24>:    cmp    DWORD PTR [rbp-0x4],0x0
   0x00000000004005e3 <+28>:    jne    0x4005ea <calc_gcd+35>
   0x00000000004005e5 <+30>:    mov    eax,DWORD PTR [rbp-0x18]
   0x00000000004005e8 <+33>:    jmp    0x4005f9 <calc_gcd+50>
   0x00000000004005ea <+35>:    mov    edx,DWORD PTR [rbp-0x4]
   0x00000000004005ed <+38>:    mov    eax,DWORD PTR [rbp-0x18]
   0x00000000004005f0 <+41>:    mov    esi,edx
   0x00000000004005f2 <+43>:    mov    edi,eax
   0x00000000004005f4 <+45>:    call   0x4005c7 <calc_gcd>
   0x00000000004005f9 <+50>:    leave
   0x00000000004005fa <+51>:    ret

And with Clang 6.0.0:

   0x0000000000400540 <+0>:     push   rbp
   0x0000000000400541 <+1>:     mov    rbp,rsp
   0x0000000000400544 <+4>:     sub    rsp,0x20
   0x0000000000400548 <+8>:     mov    DWORD PTR [rbp-0x8],edi
   0x000000000040054b <+11>:    mov    DWORD PTR [rbp-0xc],esi
   0x000000000040054e <+14>:    mov    eax,DWORD PTR [rbp-0x8]
   0x0000000000400551 <+17>:    cdq
   0x0000000000400552 <+18>:    idiv   DWORD PTR [rbp-0xc]
   0x0000000000400555 <+21>:    mov    DWORD PTR [rbp-0x10],edx
   0x0000000000400558 <+24>:    cmp    DWORD PTR [rbp-0x10],0x0
   0x000000000040055c <+28>:    jne    0x40056d <calc_gcd+45>
   0x0000000000400562 <+34>:    mov    eax,DWORD PTR [rbp-0xc]
   0x0000000000400565 <+37>:    mov    DWORD PTR [rbp-0x4],eax
   0x0000000000400568 <+40>:    jmp    0x40057b <calc_gcd+59>
   0x000000000040056d <+45>:    mov    edi,DWORD PTR [rbp-0xc]
   0x0000000000400570 <+48>:    mov    esi,DWORD PTR [rbp-0x10]
   0x0000000000400573 <+51>:    call   0x400540 <calc_gcd>
   0x0000000000400578 <+56>:    mov    DWORD PTR [rbp-0x14],eax
   0x000000000040057b <+59>:    mov    eax,DWORD PTR [rbp-0x4]
   0x000000000040057e <+62>:    add    rsp,0x20
   0x0000000000400582 <+66>:    pop    rbp
   0x0000000000400583 <+67>:    ret

You can see that the compilers made different choices in optimizations.

In fact, with GCC, rbp-0x18 is equal to 0x00000005 by +38. With Clang, rbp-0x4 is equal to something entirely different. On my machine, in pwndbg, it was 0x00007fff.

Solution 2:[2]

Your code is bugged. It is allowed to return from an int function without returning a value only if the value will never be consumed. This is a holdover from K&R C that should no longer be used.

int calc_gcd (int a, int b) {
    int r = a % b;
    if (r == 0) {
        return b;
    } else {
        calc_gcd (b, r);
    }
}

Clearly incorrect. You want.

int calc_gcd (int a, int b) {
    int r = a % b;
    if (r == 0) {
        return b;
    } else {
        return calc_gcd (b, r);
    }
}

In fact this specific code tends to work at -O0 because the return value is left over left over in the register is the return value you want.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Oliver
Solution 2 Joshua