C旧式与新式函数的声明与定义

关于C语言函数的原型声明与函数定义,这里涉及了许多的内容,以此文章,记录自己所了解的知识,以备日后查阅,同时也帮助自己和大家了解这当中暗含的“陷阱”。

由于历史的原因,C语言的函数声明有旧式和新式之分,旧式就是K&R,而新式则是ANSI,如下图:
C旧式与新式函数的声明与定义_第1张图片

现在倡导的是使用后者,而不要使用前者,对于K&R,由于存在大量旧式代码,为了保持兼容,所以没有被正式废弃。
这两者在参数传递的时会有所区别,在K&R中,由于函数的参数也是表达式,所以会发生类型提升,即传递一个短于int的整数,函数实际所接收到的是int,如果传递的是float,函数实际接收到的是double,在被调用函数的函数体内,这些值会根据函数定义时参数的声明类型自动裁减为该类型。
你可能会感到困惑,为什么不嫌麻烦将它们提升为更大的类型,然后又直接把它们裁减为原来的大小?之所以这样做,原意是为了简化编译器—所有的东西都是同一长度。如果只固定使用几种类型,将大大简化参数的传递。所有的参数都统一为标准长度,被调用函数会根据需要对它们进行裁剪。
相反在ANSI中,如果使用了适当的函数原型,类型提升便不会发生,如果参数声明为char,则实际传递的也是char。使用新风格的函数定义,编译器就会假定参数是准确声明的,于是便不进行类型提升,并据此产生代码。

关于声明和定义,我们需要考虑4种情况

  1. K&R C函数声明和K&R C函数定义
    能够顺利调用,所传递的参数会进行类型提升
  2. ANSI C函数声明(原型)和ANSI C函数定义
    能够顺利调用,所传递的参数位实际参数
  3. ANSI C函数声明(原型)和K&R C函数定义
    如果使用一个较窄的类型就会失败,函数调用时所传递的是实际类型,而函数期望接收的是提升后的类型
  4. K&R C函数声明和ANSI C函数定义
    如果使用一个较窄的类型就会失败,函数调用时所传递的是提升后的类型,而函数期望接收的是实际类型

所以,如果为一个K&R C函数定义增加函数原型,而原型的参数列表中有一个short参数,在参数传递时,这个原型将导致实际传递给函数的就是short类型的参数,而根据函数的定义,它期望接收的是一个int类型的参数。这样,函数从堆栈中抓取4个字节(int)而不是2个字节(short)。

下面展示两种失败情况

/* 文件1 */
/* 旧风格的定义,但它具有原型 */
int olddef (float d, char i);

int main(void)
{
    float d = 10.0;
    char j = 3;

    olddef (d, j);

    /* 新风格的定义,但它没有原型 */
    newdef (d, j); 
}

----------------------------------------------------------------

/* 文件2 */
/* 旧风格的定义,但它具有原型 */
olddef(d , i)
float d;
char i;
{
    printf ("olddef: float = %f, char = %x \n", d, i);
}

/* 新风格的定义,但它没有原型 */
newdef(float d, char i)
{
    printf ("newdef: float = %f, char = %x \n", d, i);  
}

打印结果如下
这里写图片描述

所以,坚决不要在函数的声明和定义中混用新旧两种风格。

还有一种错误的用法:

int main(void)
{
    union
    {
        double d;
        float f;
    }u;
    u.d = 10.0;
    printf ("put in a double, pull out a float f = %f \n", u.f);

    u.f = 10.0;
    printf ("put in a float, pull out a double d = %f \n", u.d);
}

打印的结果为:
这里写图片描述

你可能感兴趣的:(ansi,函数声明,函数定义,K-R)