K&R 中的 C 函数声明

13

我不熟悉K&R风格的函数声明。

以下代码可以编译通过,但会有一个警告(只与-Wall相关的主函数返回值),但是使用的变量的数据类型是什么?

main(a, b, c, d){
    printf("%d", d);
}

foo(a, b){
     a = 2; 
     b = 'z';
}

如果这个问题以前已经被问过,请在评论区提供链接。我找不到相似的东西。

编辑

我刚刚遇到了一段混淆的C代码,其中使用了这些语法。
但我可以保证,在C编程中我不会使用这样的语法。


2
你收到了哪些警告?这将有助于回答你的问题。 - Glenn
奖励:尝试调用 foo("this", "shouldn't", "work") - nneonneo
@nneonneo foo(a,b,c){printf("%s-%s-%s", a,b,c); } main(a,b,c,d){foo("this", "shouldn't", "work");} 编译并打印输出! - P0W
K&R有两个版本。第一个版本显然很老了。第二个版本讨论并专注于ANSI C。这种旧风格在K&R第二版中没有讨论。 - JackCColeman
1
我也不知道,好问题,但超过了今天的投票限制。 - Grijesh Chauhan
显示剩余2条评论
4个回答

22

“K&R C”是指Kernighan & Ritchie的书“The C Programming Language”的1978年第一版所定义的语言。

在K&R(即ANSI之前)的C中,实体通常可以在没有显式类型的情况下声明,并且将默认为类型int。这追溯到C的祖先语言B和BCPL。

main(a,b,c,d){
    printf("%d", d);
}

那几乎相当于:

int main(int a, int b, int c, int d) {
    printf("%d", d);
}

在 ANSI C (1989) 和 ISO C (1990) 中,“implicit int”规则仍然是合法但已经过时的,但是 1999 年的 ISO C 标准取消了该规则(同时保留了旧式声明和定义语法)。

需要注意的是,我说它“几乎”等同。从定义的角度来看,它本质上是相同的,但从声明的角度来看,它不提供参数类型信息。使用旧式定义时,对于参数数量或类型不正确的调用无需进行诊断; 它只是未定义行为。与可见原型不同,不匹配的参数会触发编译时诊断 - 并且可能会将参数隐式转换为参数类型。

由于这是对 main 的定义,因此存在另一个问题。标准仅指定了两种形式的 main(一种没有参数,一种带有两个参数 argcargv)。实现可能支持其他形式,但四个 int 参数的形式不太可能是其中之一。因此程序的行为未定义。实际上,在初始调用中,d 可能具有某些垃圾值。(是的,在 C 中递归调用 main 是允许的,但很少是一个好主意。)

foo(a,b){
    a = 2;
    b = 'z';
}

这几乎相当于:

int foo(int a, int b) {
    a = 2;
    b = 'z';
}

(请注意,'z' 的类型是 int 而不是 char。)

再次强调,旧的形式不能对参数类型进行检查,因此类似以下的调用:

foo("wrong type and number of arguments", 1.5, &foo);

无需进行诊断。

底线:了解K&R样式的函数声明和定义很好。仍然有旧代码使用它们,它们在C2011中仍然合法(但已过时,没有“implicit int”规则)。但是几乎没有任何好理由编写使用它们的代码(除非你被迫使用非常旧的编译器,但这种情况很罕见并且越来越少)。

但我可以向您保证,在C编程中我不会使用这样的语法。

太好了!


8
在K&R风格的函数定义中,参数的类型由一组专用的声明指定,这些声明位于函数“签名”本身和实际函数体之间。例如,下面是一个函数定义的例子:
void foo(a, b, c)
double a;
char b;
{
  ...
}

该函数使用参数类型为double, charint。这实际上就是"隐式int"规则的发挥方式和位置:由于参数c在上述声明列表中没有被提及,它被假定为具有类型int

请注意一个重要的细节,我认为其他答案没有很清楚地表明:参数c的类型为int不是因为函数参数列表中缺少该类型,而是因为在函数"签名"(函数体之前)后面的声明符序列中没有提到它。在K&R风格的声明中,函数参数列表中总是缺少类型(这是K&R声明的定义特征),但这并不意味着所有参数都被假定为具有类型int

附注:请注意,C99仍然支持K&R风格的声明,但由于C99禁止了"隐式int"规则,因此需要在函数"签名"后的声明列表中提及所有函数参数。由于上述示例没有在声明列表中添加int c,所以在C99中不会编译通过。


+1 感谢您上次的澄清。您能否举个例子,说明在哪些情况下不会默认使用 int 类型? - P0W
@P0W:我不确定你的意思。我的回答是,在上面的例子中,b的类型并不是默认为int,只是因为函数参数列表中缺少了它的类型。为了确定b的类型,编译器会继续查找,而不是草率下结论。它会发现char b的声明,并意识到b应该是一个char类型。 - AnT stands with Russia

4
在C89中,变量的默认类型是int:它被定义为隐式int。这个规则在C99中被撤销了。
在您的示例中,它将被编译为:
main(int a,int b,int c,int d){printf("%d", d);}

foo(int a,int b){a=2; b='z';}

3
差不多了。实际上,它是编译成 main(a,b,c,d) int a,b,c,d; 的形式。两者之间的差别微妙,但在使用 K&R 语法时,你可以做一些愚蠢的事情,比如用任何数量和类型的参数调用这些函数而没有警告(例如,参见 https://dev59.com/LGMl5IYBdhLWcg3wsIuA)。 - nneonneo
这段代码有什么合理性呢?foo(a,b,c){printf("%s-%s-%s", a,b,c); } main(a,b,c,d){foo("this", "shouldn't", "work");}它就是能够正常运行。 - P0W
@P0W,当我尝试编译和运行你的代码片段时,它会出现段错误。你能成功运行吗? - aymericbeaumet
@abeaumet 看起来是未定义行为 (UB),使用 -Wall 会抛出警告。 - P0W
@P0W 在某些系统上,int和指针具有相同的内存大小。因此,在某些特定情况下可能会起作用。当然,这会生成警告。尝试添加“-Wextra”。 - aymericbeaumet
提高严格性级别,您将会收到对 printf 的警告,提示警告:格式参数不是指针(arg 2) - Dayal rai

4

在C语言中,默认参数是int类型的。有关K&R语法,请参见这里这里


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接