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

发布于 2024-08-08 21:07:03 字数 543 浏览 10 评论 0原文

这种 C 语法有什么用处——使用“K&R”风格的函数声明?

int func (p, p2)
    void* p;
    int  p2;
{
    return 0;
}

我能够在 Visual Studios 2010beta 中编写此内容,但

// 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吗?

What is useful about this C syntax — using 'K&R' style function declarations?

int func (p, p2)
    void* p;
    int  p2;
{
    return 0;
}

I was able to write this in Visual Studios 2010beta

// yes, the arguments are flipped
void f()
{
    void* v = 0;
    func(5, v);
}

I don't understand. What's the point of this syntax? I can write:

int func (p, p2)
    int  p2;
{
    return 0;
}
// and write
int func (p, p2)
{
    return 0;
}

The only thing it seems to specify is how many parameters it uses and the return type. I guess parameters without types is kind of cool, but why allow it and the int paranName after the function declarator? It's weird.

Also is this still standard C?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

影子是时光的心 2024-08-15 21:07:03

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

第一部分是函数定义中使用的奇怪语法,

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

这实际上是 K&R-样式函数定义。其他答案已经很好地涵盖了这一点。事实上,这并没有太多。该语法已被弃用,但即使在 C18 中仍然完全支持(除了 C99 或更高版本中的“无隐式 int”规则,这意味着在 C99 中您不能省略 p2 的声明)。

第二部分与K&R风格关系不大。我指的是可以使用“交换”参数调用该函数的事实,即在此类调用中不会进行参数类型检查。这与 K&R 风格的定义本身关系不大,但与没有原型的函数密切相关。您会看到,在 C 中,当您声明这样的函数时,

int foo();

它实际上声明了一个函数 foo,该函数采用未知类型的未指定数量的参数。你可以称它为 as

foo(2, 3);

和 as

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 as

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

并且不会出现任何约束冲突。 (通常,质量编译器会给您警告)。

另外,一个有时被忽视的事实是,这

int f()
{
  return 0;
}

也是不引入原型的 K&R 风格的函数定义。为了使其“现代”,您必须在参数列表中放置一个显式的 void

int f(void)
{
  return 0;
}

最后,与流行的看法相反,K&R 风格的函数定义和非原型函数声明都是完全C99 及更高版本支持(最高可达 C18,但 C23 不支持)。如果我没记错的话,前者自 C89/90 以来已被弃用。 C99要求函数在第一次使用之前声明,但声明不要求是原型。这种混乱显然源于流行的术语混淆:许多人将任何函数声明称为“原型”,而事实上,“函数声明”与“原型”不是一回事。

请注意,C23 将更改规则。 K&R 风格的函数定义将不再是标准 C 语言。此外,诸如 extern int func(); 之类的函数声明将声明与 extern int func(void) 等价的函数原型; — 就像 C++ 中一样。而诸如 int func() { … } 之类的函数定义将为该函数定义一个原型。

The question you are asking is really two questions, not one. Most replies so far have tried to cover the entire thing with a generic blanket "this is K&R style" answer, while, in fact, only a small part of it has anything to do with what is known as K&R style (unless you see the entire C language as "K&R-style" in one way or another :)

The first part is the strange syntax used in the function definition

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

This one is actually a K&R-style function definition. Other answers have covered this pretty well. And there's not much to it, actually. The syntax is deprecated but still fully supported even in C18 (except for "no implicit int" rule in C99 or later, meaning that in C99 you can't omit the declaration of p2).

The second part has little to do with K&R-style. I refer to the fact that the function can be called with "swapped" arguments, i.e. no parameter type checking takes place in such a call. This has very little to do with K&R-style definition per se, but it has everything to do with your function having no prototype. You see, in C, when you declare a function like this

int foo();

it actually declares a function foo that takes an unspecified number of parameters of unknown type. You can call it as

foo(2, 3);

and as

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

and so on (you get the idea);

Only the call with proper arguments will "work" (meaning that the others produce undefined behavior), but it is entirely up to you to ensure its correctness. The compiler is not required to diagnose the incorrect ones, even if it somehow magically knows the correct parameter types and their total number.

Actually, this behavior is a feature of C language. A dangerous one, but a feature nevertheless. It allows you to do something like this:

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]();
}

i.e. mix different function types in a "polymorphic" array without any typecasts (variadic function types can't be used here, though). Again, the inherent dangers of this technique are quite obvious (I don't remember ever using it, but I can imagine where it can be useful), but that's C after all.

Finally, the bit that links the second part of the answer to the first. When you make a K&R-style function definition, it doesn't introduce a prototype for the function. As far as function type is concerned, your func definition declares func as

int func();

i.e. neither the types nor the total number of parameters are declared. In your original post, you say, "... it seems to specify is how many params it uses ...". Formally speaking, it doesn't! After your two-parameter K&R-style func definition, you still can call func as

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

and there won't be any constraint violation in it. (Normally, a quality compiler will give you a warning).

Also, a sometimes overlooked fact is that

int f()
{
  return 0;
}

is also a K&R-style function definition that does not introduce a prototype. To make it "modern" you'd have to put an explicit void in the parameter list:

int f(void)
{
  return 0;
}

Finally, contrary to popular belief, both K&R-style function definitions and non-prototyped function declarations are fully supported in C99 and beyond (up to C18, but not in C23). The former has been deprecated since C89/90, if I remember correctly. C99 requires the function to be declared before the first use, but the declaration is not required to be a prototype. The confusion apparently stems from the popular terminological mix-up: many people call any function declaration "a prototype", while, in fact, "function declaration" is not the same thing as "prototype".

Note that C23 will change the rules. The K&R style function definition will no longer be standard C. Further, a function declaration such as extern int func(); will declare a function prototype equivalent to extern int func(void); — just as in C++. And a function definition such as int func() { … } will define a prototype for the function.

伴梦长久 2024-08-15 21:07:03

这是相当古老的 K&RC 语法(早于 ANSI/ISO C)。现在,您不应该再使用它(因为您已经注意到它的主要缺点:编译器不会为您检查参数的类型)。在您的示例中,参数类型实际上默认为 int

当时,使用这种语法,有时会发现这样的函数

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

实际上是一个有效的定义,如 pq 的类型以及 p 的返回类型。 code>foo 默认为 int

This is pretty old K&R C syntax (pre-dates ANSI/ISO C). Nowadays, you should not use it anymore (as you have already noticed its major disadvantage: the compiler won't check the types of arguments for you). The argument type actually defaults to int in your example.

At the time, this syntax was used, one sometimes would find functions like

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

which was actually a valid definition, as the types for p, q, and the return type of foo default to int.

夜访吸血鬼 2024-08-15 21:07:03

这只是一种旧语法,早于您可能更熟悉的“ANSI C”语法。通常称为“K&R C”。

当然,编译器支持它的完整性,并且能够处理旧的代码库。

This is simply an old syntax, that pre-dates the "ANSI C" syntax you might be more familiar with. It's called "K&R C", typically.

Compilers support it to be complete, and to be able to handle old code bases, of course.

千年*琉璃梦 2024-08-15 21:07:03

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

This is the original K&R syntax before C was standardized in 1989. C89 introduced function prototypes, borrowed from C++, and deprecated the K&R syntax. There is no reason to use it (and plenty of reasons not to) in new code.

兔姬 2024-08-15 21:07:03

这是 C 没有函数原型时的遗留物。那时,(我认为)函数被假定为返回 int,并且其所有参数都被假定为 int。没有对函数参数进行检查。

使用当前 C 语言中的函数原型会更好。
并且您必须在C99中使用它们(C89仍然接受旧语法)。

并且C99需要声明函数(可能没有原型)。如果您从头开始编写一个新函数,则需要提供一个声明……也使其成为一个原型:您不会丢失任何内容,并会从编译器获得额外的检查。

That's a relic from when C had no prototypes for functions. Way back then, (I think) functions were assumed to return int and all its arguments were assumed to be int. There was no checking done on function parameters.

You're much better off using function prototypes in the current C language.
And you must use them in C99 (C89 still accepts the old syntax).

And C99 requires functions to be declared (possibly without a prototype). If you're writing a new function from scratch, you need to provide a declaration ... make it a prototype too: you lose nothing and gain extra checking from the compiler.

×纯※雪 2024-08-15 21:07:03

不幸的是,里奇已经去世了,所以我们不能再问他了。然而,查看 C 的历史记录可以提供一些非常重要的线索,可以回答您最初的问题。这个答案是基于证据的推论。

C 源于一种称为 B 的语言,而 B 又是 BCPL 的语法简化版本。 BCPL 是一种仅对整数进行操作的语言;也就是说,在这种语言中,您可以使用的唯一数据类型正是单个机器字的位数。在 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 语言才支持多种类型,当时处理嵌入机器字内的字节的开销变得更加显着。如果允许我在这里进行更多推测,Ritchie(曾与 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 的持续演变的进化产物,如果你愿意的话,也可以说是一个意外。

我希望这有助于为这种奇怪的语法建立历史背景。

Unfortunately, Ritchie has passed, so we can no longer ask him. However, looking at the documented history of C gives some really important clues that will answer your original question. This answer is an inference based on evidence.

C has its roots in a language called B, which in turn, is a syntax-simplified version of BCPL. BCPL is a language which operates on ints only; that is, the sole data type available to you in this language is exactly however many bits a single machine word is. On a 16-bit machine, BCPL lets you manipulate 16-bit values; on a 32-bit machine, 32-bit values, etc.

Correspondingly, when you define a function in BCPL, it will not have any type annotations:

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

Note that $( and $) can now be replaced with { and }, but when BCPL was most popular (mid-60s to mid-70s), not all terminal keyboards had a { and } character.

Thompson simplified BCPL's syntax to create B, so as to fit the compiler in the resource constraints on the PDP-7 he was using at the time. The LET and VALOF keywords were removed, since they didn't offer anything to the programmer or the compiler, and the block delimiters were replaced with braces, simplifying the lexer. RESULTIS's verbosity was replaced with "return", and we now get:

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

That looks like valid C code, but it could just as well be B code too. Remember that B, like BCPL, was a single-typed language.

Supporting multiple types didn't come until C, when the overhead of processing bytes embedded inside of machine words became more significant. If I'm allowed to speculate even more here, Ritchie (who had worked with Thompson to create B) most likely just re-used B's parser code and just tweaked it to support different types. Thus, it becomes fairly natural for C's syntax back then to look like this:

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

This syntax would allow more or less effortless porting of a largish body of B code into the C ecosystem of the day.

C has always been something of a fluid language, especially in its early days. Did you know C's original method for statically declaring a constant looked like this?

someVal 1024;

Talk about minimum syntax, eh? Today, this syntax is not supported, even in most "K&R" style C compilers.

So what is the value (to you) of this syntax? Nothing. Absolutely nothing. It's just an evolutionary artifact, an accident if you will, of C's heritage and its continued evolution away from BCPL.

I hope this helps establish the historical context for this quirky syntax.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文