没有 return 语句的 gcc 中的尾递归
tail recursion in gcc without return statement
显然以下代码在 GCC 中有效。我在 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;
}
但它在 clang 13
中失败,结果如下
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
我不明白。显然 GCC 允许的并不直观,您必须从函数中 return。
我尝试了以下方法,但在 gcc 中不起作用
# include <stdio.h>
int useless_func()
{
3050;
}
int main() {
printf("result = %d", useless_func());
return 0;
}
输出为result = 0
您的代码有问题。允许从 int
函数 return 不 returning 一个值,只有当该值永远不会被消耗时。这是 K&R C 的遗留物,不应再使用。
int calc_gcd (int a, int b) {
int r = a % b;
if (r == 0) {
return b;
} else {
calc_gcd (b, r);
}
}
显然不正确。你要。
int calc_gcd (int a, int b) {
int r = a % b;
if (r == 0) {
return b;
} else {
return calc_gcd (b, r);
}
}
事实上,这个特定的代码倾向于在 -O0
上工作,因为 return 值被遗留在寄存器中,剩下的就是你想要的 return 值。
TL;DR: 你老师给你的GCD函数是不可移植的。
你想要的如下:
int calc_gcd (int a, int b) {
int r = a % b;
if (r == 0) {
return b;
} else {
return calc_gcd (b, r);
}
}
您使用的是什么版本的 GCC,在什么操作系统上?
GCC 比 Clang 宽松得多。看起来 GCC 决定函数 calc_gcd
不应该失败并且应该 return calc_gcd(b, r)
.
的结果
当您使用 Clang 编译时,我很惊讶它没有 return 1
。当我在 M1 MacBook Pro 上编译时它做到了。
来自 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.
在 x86_64 Linux 上,当调用 return 具有 non-void 值的函数时,space 被分配到 return 值。因此,没有明确的 return 声明,一些值仍然是 returned.
注意:在x86_64上,return值存储在RAX中(EAX是低位)。
我在 x86_64 Ubuntu 服务器 18.04 上使用 GCC 7.5.0 编译。
我使用的命令是gcc -o gcd gcd.c -no-pie -g
。
这是 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
使用 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
你可以看到编译器在优化方面做出了不同的选择。
事实上,对于 GCC,rbp-0x18
等于 0x00000005
+38。
对于 Clang,rbp-0x4
等于完全不同的东西。在我的机器上,在 pwndbg 中,它是 0x00007fff
.
显然以下代码在 GCC 中有效。我在 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;
}
但它在 clang 13
中失败,结果如下
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
我不明白。显然 GCC 允许的并不直观,您必须从函数中 return。
我尝试了以下方法,但在 gcc 中不起作用
# include <stdio.h>
int useless_func()
{
3050;
}
int main() {
printf("result = %d", useless_func());
return 0;
}
输出为result = 0
您的代码有问题。允许从 int
函数 return 不 returning 一个值,只有当该值永远不会被消耗时。这是 K&R C 的遗留物,不应再使用。
int calc_gcd (int a, int b) {
int r = a % b;
if (r == 0) {
return b;
} else {
calc_gcd (b, r);
}
}
显然不正确。你要。
int calc_gcd (int a, int b) {
int r = a % b;
if (r == 0) {
return b;
} else {
return calc_gcd (b, r);
}
}
事实上,这个特定的代码倾向于在 -O0
上工作,因为 return 值被遗留在寄存器中,剩下的就是你想要的 return 值。
TL;DR: 你老师给你的GCD函数是不可移植的。
你想要的如下:
int calc_gcd (int a, int b) {
int r = a % b;
if (r == 0) {
return b;
} else {
return calc_gcd (b, r);
}
}
您使用的是什么版本的 GCC,在什么操作系统上?
GCC 比 Clang 宽松得多。看起来 GCC 决定函数 calc_gcd
不应该失败并且应该 return calc_gcd(b, r)
.
当您使用 Clang 编译时,我很惊讶它没有 return 1
。当我在 M1 MacBook Pro 上编译时它做到了。
来自 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.
在 x86_64 Linux 上,当调用 return 具有 non-void 值的函数时,space 被分配到 return 值。因此,没有明确的 return 声明,一些值仍然是 returned.
注意:在x86_64上,return值存储在RAX中(EAX是低位)。
我在 x86_64 Ubuntu 服务器 18.04 上使用 GCC 7.5.0 编译。
我使用的命令是gcc -o gcd gcd.c -no-pie -g
。
这是 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
使用 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
你可以看到编译器在优化方面做出了不同的选择。
事实上,对于 GCC,rbp-0x18
等于 0x00000005
+38。
对于 Clang,rbp-0x4
等于完全不同的东西。在我的机器上,在 pwndbg 中,它是 0x00007fff
.