这个C
语法有什么用-使用'K&R'风格的函数声明?
int func (p, p2)
void* p;
int p2;
{
return 0;
}
我可以在Visual Studios 2010测试版中写这个
// yes, the arguments are flipped
void f()
{
void* v = 0;
func(5, v);
}
我不明白。这个语法有什么意义?我可以写:
int func (p, p2)
int p2;
{
return 0;
}
// and write
int func (p, p2)
{
return 0;
}
它唯一指定的是使用多少参数和返回类型,我想没有类型的参数很酷,但是为什么允许它和函数声明符后面的int paranName
呢?这很奇怪。
这还是标准C吗?
6条答案
按热度按时间4xrmg8kj1#
你所问的问题实际上是两个问题,而不是一个。到目前为止,大多数回复都试图用一个通用的“这是K&R风格”的答案来覆盖整个问题,而事实上,只有一小部分与所谓的K&R风格有关(除非你以某种方式将整个C语言视为“K& R风格”:)
第一部分是函数 definition 中使用的奇怪语法
这实际上是一个K& R风格的函数定义。其他的答案已经很好地涵盖了这一点。实际上,它并没有太多。语法已经被弃用,但即使在C99中仍然完全支持(除了C99中的“no implicit int”规则,这意味着在C99中你不能省略
p2
的声明)。第二部分与K& R风格没有什么关系。我指的是这样一个事实,即函数可以用“交换”参数调用,即在这样的调用中不进行参数类型检查。这与K& R风格定义本身没有什么关系,但它与你的函数没有原型有很大关系。你看,在C中,当你声明这样的函数时
它实际上声明了一个函数
foo
,该函数接受 * 未知类型的未知数量的参数 *。并作为
等等(你明白的);
只有带有正确参数的调用才能“工作”(意味着其他调用产生未定义的行为),但确保其正确性完全取决于您。编译器不需要诊断不正确的调用,即使它不知何故神奇地知道正确的参数类型及其总数。
实际上,这种行为是C语言的一个“特性”。一个危险的特性,但仍然是一个特性。它允许你做这样的事情
也就是说,在一个“多态”数组中混合不同的函数类型,而不进行任何类型转换(可变变量函数类型不能在这里使用)。同样,这种技术的内在危险是非常明显的(我不记得曾经使用过它,但我可以想象它在哪里会有用),但这毕竟是C语言。
最后,将答案的第二部分与第一部分联系起来的那一点。当你做一个K& R风格的函数定义时,它不会引入函数的原型。就函数类型而言,你的
func
定义将func
声明为也就是说,既没有声明类型,也没有声明参数的总数。在你最初的帖子中,你说“......它似乎指定了它使用多少参数......"。从形式上讲,它没有!在你的两参数K& R风格的
func
定义之后,你仍然可以调用func
作为并且其中不会有任何约束冲突(通常,高质量的编译器会给予你一个警告)。
另外,一个有时被忽视的事实是
也是一个K& R风格的函数定义,没有引入原型。要使它“现代化”,你必须在参数列表中放置一个显式的
void
最后,与流行的看法相反,K& R风格的函数定义和非原型函数声明在C99中都完全支持。如果我没记错的话,前者从C89/90开始就被弃用了。C99要求函数在首次使用之前声明,但声明不要求是原型。这种混淆显然源于流行的术语混淆:许多人称任何函数声明为“原型”,而实际上“函数声明”与“原型”不是一回事。
pu82cl6c2#
这是一种非常古老的K&R C语法(早于ANSI/ISO C)。现在,您不应该再使用它了(因为您已经注意到它的主要缺点:编译器不会为你检查参数的类型)。在你的例子中,参数类型实际上默认为
int
。在使用这种语法的时候,有时会发现如下函数
这实际上是一个有效的定义,因为
p
、q
的类型和foo
的返回类型默认为int
。fslejnso3#
这只是一个很老的语法,它比你可能更熟悉的“ANSIC”语法要早,通常叫做“K& RC”。
当然,编译器支持它的完整性,并且能够处理旧的代码库。
clj7thdc4#
这是C语言在1989年标准化之前的原始K&R语法。C89引入了函数原型,借用了C++,并弃用了K&R语法。在新代码中没有理由使用它(也有很多理由不使用)。
eulz3vhy5#
这是C语言没有函数原型的时代遗留下来的东西,很久以前,(我认为)函数被假定为返回
int
,它的所有参数都被假定为int
,没有对函数参数进行检查。您最好在当前的C语言中使用函数原型。
而且您必须在C99中使用它们(C89仍然接受旧语法)。
C99要求函数要声明(可能没有原型)。如果你从头开始写一个新函数,你需要提供一个声明...让它也成为一个原型:您不会丢失任何东西,并且可以从编译器中获得额外的检查。
omvjsjqw6#
不幸的是,里奇已经去世了,所以我们不能再问他了,但是,看看C有文献记载的历史,给出了一些真正重要的线索,这些线索会回答你最初的问题,这个答案是基于证据的推断。
C源于一种叫做B的语言,B是BCPL的语法简化版本。BCPL是一种只对int**进行操作的语言。也就是说,在这种语言中,唯一可用的数据类型就是单个机器字的位数。2在16位机器上,BCPL允许你操作16位值;在32位机器上,32位值等。
相应地,当你在BCPL中定义一个函数时,它不会有任何类型注解:
请注意,$(和$)现在可以用{和}替换,但在BCPL最流行的时候(60年代中期到70年代中期),并不是所有的终端键盘都有{和}字符。
Thompson简化了BCPL的语法来创建B,以使编译器适应他当时使用的PDP-7上的资源约束。LET和VALOF关键字被删除,因为它们没有为程序员或编译器提供任何东西,块分隔符被替换为大括号,简化了词法分析器。RESULTIS的冗长被替换为“return”,我们现在得到:
这看起来像是有效的C代码,但它也可能是B代码。记住,B和BCPL一样,是单类型语言。
支持多种类型直到C才出现,那时处理嵌入在机器字中的字节的开销变得越来越大。如果允许我在这里进行更多的推测,里奇(他曾与Thompson合作创建了B)很可能只是重用了B的解析器代码,并对其进行了调整以支持不同的类型。因此,C的语法看起来像这样变得相当自然:
这种语法允许或多或少地将大量的B代码移植到当今的C生态系统中。
C语言一直是一种流动的语言,特别是在它的早期。你知道C语言最初静态声明常量的方法是这样的吗?
谈论最小语法,嗯?今天,这种语法是不支持的,即使在大多数“K&R”风格的C编译器中也是如此。
那么这个语法对你来说有什么价值呢?没有,绝对没有。它只是一个进化的产物,如果你愿意的话,这是一个意外,是C语言的遗产和它远离BCPL的持续进化。
我希望这有助于为这个古怪的语法建立历史背景。