函数声明与原型的替代(K&R)C语法

wbrvyc0a  于 2023-03-17  发布在  其他
关注(0)|答案(6)|浏览(181)

这个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吗?

4xrmg8kj

4xrmg8kj1#

你所问的问题实际上是两个问题,而不是一个。到目前为止,大多数回复都试图用一个通用的“这是K&R风格”的答案来覆盖整个问题,而事实上,只有一小部分与所谓的K&R风格有关(除非你以某种方式将整个C语言视为“K& R风格”:)
第一部分是函数 definition 中使用的奇怪语法

int func(p, p2)
void *p;
int  p2; /* <- optional in C89/90, but not in C99 */
{
  return 0;
}

这实际上是一个K& R风格的函数定义。其他的答案已经很好地涵盖了这一点。实际上,它并没有太多。语法已经被弃用,但即使在C99中仍然完全支持(除了C99中的“no implicit int”规则,这意味着在C99中你不能省略p2的声明)。
第二部分与K& R风格没有什么关系。我指的是这样一个事实,即函数可以用“交换”参数调用,即在这样的调用中不进行参数类型检查。这与K& R风格定义本身没有什么关系,但它与你的函数没有原型有很大关系。你看,在C中,当你声明这样的函数时

int foo();

它实际上声明了一个函数foo,该函数接受 * 未知类型的未知数量的参数 *。

foo(2, 3);

并作为

j = foo(p, -3, "hello world");

等等(你明白的);
只有带有正确参数的调用才能“工作”(意味着其他调用产生未定义的行为),但确保其正确性完全取决于您。编译器不需要诊断不正确的调用,即使它不知何故神奇地知道正确的参数类型及其总数。
实际上,这种行为是C语言的一个“特性”。一个危险的特性,但仍然是一个特性。它允许你做这样的事情

void foo(int i);
void bar(char *a, double b);
void baz(void);

int main()
{
  void (*fn[])() = { foo, bar, baz };
  fn[0](5);
  fn[1]("abc", 1.0);
  fn[2]();
}

也就是说,在一个“多态”数组中混合不同的函数类型,而不进行任何类型转换(可变变量函数类型不能在这里使用)。同样,这种技术的内在危险是非常明显的(我不记得曾经使用过它,但我可以想象它在哪里会有用),但这毕竟是C语言。
最后,将答案的第二部分与第一部分联系起来的那一点。当你做一个K& R风格的函数定义时,它不会引入函数的原型。就函数类型而言,你的func定义将func声明为

int func();

也就是说,既没有声明类型,也没有声明参数的总数。在你最初的帖子中,你说“......它似乎指定了它使用多少参数......"。从形式上讲,它没有!在你的两参数K& R风格的func定义之后,你仍然可以调用func作为

func(1, 2, 3, 4, "Hi!");

并且其中不会有任何约束冲突(通常,高质量的编译器会给予你一个警告)。
另外,一个有时被忽视的事实是

int f()
{
  return 0;
}

也是一个K& R风格的函数定义,没有引入原型。要使它“现代化”,你必须在参数列表中放置一个显式的void

int f(void)
{
  return 0;
}

最后,与流行的看法相反,K& R风格的函数定义和非原型函数声明在C99中都完全支持。如果我没记错的话,前者从C89/90开始就被弃用了。C99要求函数在首次使用之前声明,但声明不要求是原型。这种混淆显然源于流行的术语混淆:许多人称任何函数声明为“原型”,而实际上“函数声明”与“原型”不是一回事。

pu82cl6c

pu82cl6c2#

这是一种非常古老的K&R C语法(早于ANSI/ISO C)。现在,您不应该再使用它了(因为您已经注意到它的主要缺点:编译器不会为你检查参数的类型)。在你的例子中,参数类型实际上默认为int
在使用这种语法的时候,有时会发现如下函数

foo(p, q) 
{
    return q + p;
}

这实际上是一个有效的定义,因为pq的类型和foo的返回类型默认为int

fslejnso

fslejnso3#

这只是一个很老的语法,它比你可能更熟悉的“ANSIC”语法要早,通常叫做“K& RC”。
当然,编译器支持它的完整性,并且能够处理旧的代码库。

clj7thdc

clj7thdc4#

这是C语言在1989年标准化之前的原始K&R语法。C89引入了函数原型,借用了C++,并弃用了K&R语法。在新代码中没有理由使用它(也有很多理由不使用)。

eulz3vhy

eulz3vhy5#

这是C语言没有函数原型的时代遗留下来的东西,很久以前,(我认为)函数被假定为返回int,它的所有参数都被假定为int,没有对函数参数进行检查。

您最好在当前的C语言中使用函数原型。

而且您必须在C99中使用它们(C89仍然接受旧语法)。
C99要求函数要声明(可能没有原型)。如果你从头开始写一个新函数,你需要提供一个声明...让它也成为一个原型:您不会丢失任何东西,并且可以从编译器中获得额外的检查。

omvjsjqw

omvjsjqw6#

不幸的是,里奇已经去世了,所以我们不能再问他了,但是,看看C有文献记载的历史,给出了一些真正重要的线索,这些线索会回答你最初的问题,这个答案是基于证据的推断。
C源于一种叫做B的语言,B是BCPL的语法简化版本。BCPL是一种只对int**进行操作的语言。也就是说,在这种语言中,唯一可用的数据类型就是单个机器字的位数。2在16位机器上,BCPL允许你操作16位值;在32位机器上,32位值等。
相应地,当你在BCPL中定义一个函数时,它不会有任何类型注解:

LET qux(a,b,c) = VALOF $(
    /* ...etc... */
    RESULTIS someValueHere;
$)

请注意,$(和$)现在可以用{和}替换,但在BCPL最流行的时候(60年代中期到70年代中期),并不是所有的终端键盘都有{和}字符。
Thompson简化了BCPL的语法来创建B,以使编译器适应他当时使用的PDP-7上的资源约束。LET和VALOF关键字被删除,因为它们没有为程序员或编译器提供任何东西,块分隔符被替换为大括号,简化了词法分析器。RESULTIS的冗长被替换为“return”,我们现在得到:

qux(a,b,c) {
  /* ...etc... */
  return someValueHere;
}

这看起来像是有效的C代码,但它也可能是B代码。记住,B和BCPL一样,是单类型语言。
支持多种类型直到C才出现,那时处理嵌入在机器字中的字节的开销变得越来越大。如果允许我在这里进行更多的推测,里奇(他曾与Thompson合作创建了B)很可能只是重用了B的解析器代码,并对其进行了调整以支持不同的类型。因此,C的语法看起来像这样变得相当自然:

qux(a,b,c)
char a;
float b;
{
    /* ...etc... */
    return someVal;
}

这种语法允许或多或少地将大量的B代码移植到当今的C生态系统中。
C语言一直是一种流动的语言,特别是在它的早期。你知道C语言最初静态声明常量的方法是这样的吗?

someVal 1024;

谈论最小语法,嗯?今天,这种语法是不支持的,即使在大多数“K&R”风格的C编译器中也是如此。
那么这个语法对你来说有什么价值呢?没有,绝对没有。它只是一个进化的产物,如果你愿意的话,这是一个意外,是C语言的遗产和它远离BCPL的持续进化。
我希望这有助于为这个古怪的语法建立历史背景。

相关问题