C语言新式与旧式函数的声明与定义
由于历史的原因,C语言的函数声明有旧式和新式之分,旧式就是K&R,而新式则是ANSI
1. K&R C风格
/// 声明
int foo();
/// 定义
int foo(a, b)
int a;
int b;
{
//...
}
2. ANSI C风格
/// 声明
int foo(int, int);
int foo(int a, int b);
/// 定义
int foo(int a, int b)
{
//...
}
现在倡导的是使用后者,而不要使用前者,对于K&R,由于存在大量旧式代码,为了保持兼容,所以没有被正式废弃。
这两者在参数传递的时会有所区别,在K&R中,由于函数的参数也是表达式,所以会发生类型提升,即传递一个短于int的整数,函数实际所接收到的是int,如果传递的是float,函数实际接收到的是double,在被调用函数的函数体内,这些值会根据函数定义时参数的声明类型自动裁减为该类型。
你可能会感到困惑,为什么不嫌麻烦将它们提升为更大的类型,然后又直接把它们裁减为原来的大小?之所以这样做,原意是为了简化编译器—所有的东西都是同一长度。如果只固定使用几种类型,将大大简化参数的传递。所有的参数都统一为标准长度,被调用函数会根据需要对它们进行裁剪。
相反在ANSI中,如果使用了适当的函数原型,类型提升便不会发生,如果参数声明为char,则实际传递的也是char。使用新风格的函数定义,编译器就会假定参数是准确声明的,于是便不进行类型提升,并据此产生代码。
#include <stdio.h>
#include <stdlib.h>
int foo();
int main()
{
foo(1, 3);
return 0;
}
int foo(a, b)
int a;
int b;
{
int c = a + b;
printf("c = %d\n", c);
return 0;
}
对应汇编程序:
main:
push rbp
mov rbp, rsp
mov esi, 3
mov edi, 1
mov eax, 0
call foo
mov eax, 0
pop rbp
ret
.LC0:
.string "c = %d\n"
foo:
push rbp
mov rbp, rsp
sub rsp, 32
mov DWORD PTR [rbp-20], edi
mov DWORD PTR [rbp-24], esi
mov edx, DWORD PTR [rbp-20]
mov eax, DWORD PTR [rbp-24]
add eax, edx
mov DWORD PTR [rbp-4], eax
mov eax, DWORD PTR [rbp-4]
mov esi, eax
mov edi, OFFSET FLAT:.LC0
mov eax, 0
call printf
mov eax, 0
leave
ret