c - 函数显式前向声明后的准标准 C 函数头语法

标签 c language-lawyer function-prototypes

我想了解当与 ANSI 引入的显式函数原型(prototype)结合使用时,准标准“K&R 风格”函数声明语法的行为信息。具体来说,语法如下所示:

int foo(a)
    int a;
{
    /* ... */
}

与此相反:

int foo(int a) {
    /* ... */
}

请注意,我指的是函数声明语法,而不是非原型(prototype)函数的用法。

关于前一种语法如何不创建函数原型(prototype)已经做了很多。我的研究表明,如果函数定义如上,后续调用 foo(8, 6, 7, 5, 3, 0, 9) 将导致未定义的行为;而使用后一种语法,foo(8, 6, 7, 5, 3, 0, 9) 实际上是无效的。这是有道理的,但我首先明确地转发声明了我的所有功能。如果编译器不得不依赖从定义生成的原型(prototype),我已经认为这是我的代码中的一个缺陷;所以我确保使用编译器警告,如果我无法转发声明一个函数,它会通知我。

假设正确的前向声明已经到位(在本例中为 int foo(int);),K&R 函数声明语法是否仍然不安全?如果是这样,如何?新语法的使用是否否定了已经存在的原型(prototype)? At least one person显然声称在以 K&R 风格定义它们之前前向声明函数实际上非法,但我已经做到了,它编译和运行得很好。

考虑以下代码:

/*  1 */ #include <stdio.h>
/*  2 */ void f(int); /*** <- PROTOTYPE IS RIGHT HERE ****/
/*  3 */ void f(a)
/*  4 */     int a;
/*  5 */ {
/*  6 */     printf("YOUR LUCKY NUMBER IS %d\n", a);
/*  7 */ }
/*  8 */ 
/*  9 */ int main(argc, argv)
/* 10 */ int argc;
/* 11 */ char **argv;
/* 12 */ {
/* 13 */    f(1);
/* 14 */    return 0;
/* 15 */ }

当逐字输入此代码时,gcc -Wallclang -Weverything 都不会发出警告并生成程序,这些程序在运行时打印 YOUR LUCKY NUMBER IS 1 后跟一个换行符。

如果 main() 中的 f(1) 被替换为 f(1, 2), gcc 在该行发出“太多参数”错误,“此处声明”注释特别指示第 3 行,而不是第 2 行。在 clang 中,这是一个警告,不是错误,也没有说明包含声明行的注释。

如果 main() 中的 f(1) 被替换为 f("hello world"), gcc 在该行发出整数转换警告,并附有指示第 3 行的注释并显示为“预期为‘int’,但参数的类型为‘char *’”。 clang 给出了类似的错误,没有注释。

如果将main()中的f(1)替换为f("hello", "world"),则以上结果两者都按顺序给出。

我的问题是:假设函数原型(prototype)已经提供,K&R 语法是否比带有内联类型关键字的样式安全?我的研究表明的答案是,“不,一点也不”,但是对旧类型声明风格的压倒性否定,显然几乎一致的意见让我想知道我是否忽略了一些东西。有什么我忽略的吗?

最佳答案

My research indicates that, if the function were defined as above, a subsequent call foo(8, 6, 7, 5, 3, 0, 9) would result in undefined behavior; whereas with the latter syntax, foo(8, 6, 7, 5, 3, 0, 9) would actually be invalid.

这是正确的。给定 int foo(a) int a; {},根据 C 2018 6.5.2.2 6,调用具有未定义的行为:

If the expression that denotes the called function has a type that does not include a prototype,… If the number of arguments does not equal the number of parameters, the behavior is undefined.

并且,给定 int foo(int a);,根据 C 2018 6.5.2.2 2,调用违反了约束:

If the expression that denotes the called function has a type that includes a prototype, the number of arguments shall agree with the number of parameters.

Assuming that proper forward-declarations are in place (in this case, int foo(int);), is the K&R function declaration syntax still unsafe?

如果一个函数既有带原型(prototype)的声明,就像在前向声明中那样,又有没有原型(prototype)的定义(使用旧的 K&R 语法),则标识符的结果类型是原型(prototype)版本的类型。使用参数列表声明的函数类型可以与使用 K&R 语法声明的函数类型合并。首先 C 2018 6.7.6.3 15 告诉我们这两种类型是兼容的:

For two function types to be compatible, both shall specify compatible return types. … If one type has a parameter type list and the other type is specified by a function definition that contains a (possibly empty) identifier list, both shall agree in the number of parameters, and the type of each prototype parameter shall be compatible with the type that results from the application of the default argument promotions to the type of the corresponding identifier.…

然后C 2018 6.2.7 3告诉我们他们可以合并:

A composite type can be constructed from two types that are compatible; it is a type that is compatible with both of the two types and satisfies the following conditions:

— If only one type is a function type with a parameter type list (a function prototype), the composite type is a function prototype with the parameter type list.

C 2018 6.2.7 4 告诉我们标识符采用复合类型:

For an identifier with internal or external linkage declared in a scope in which a prior declaration of that identifier is visible, if the prior declaration specifies internal or external linkage, the type of the identifier at the later declaration becomes the composite type.

因此,如果您同时拥有 int foo(int a);int foo() int a; {}foo 的类型为 int foo(int a)

这意味着如果每个函数都用原型(prototype)声明,那么就调用它们的语义而言,在没有原型(prototype)的情况下定义它们与用原型(prototype)定义它们一样安全。 (我不评论样式或其他样式或多或少容易因错误编辑或与函数调用的实际语义无关的其他方面而导致的错误的可能性)。

但是请注意,原型(prototype)中的类型必须在默认参数提升之后匹配 K&R 风格定义中的类型。例如,这些类型是兼容的:

void foo(int a);
void foo(a)
char a; // Promotion changes char to int.
{
}

void bar(double a);
void bar(a)
float a; // Promotion changes float to double.
{
}

并且这些类型不是:

void foo(char a);
void foo(a)
char a; // char is promoted to int, which is not compatible with char.
{
}

void bar(float a);
void bar(a)
float a; // float is promoted to double, which is not compatible with float.
{
}

关于c - 函数显式前向声明后的准标准 C 函数头语法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55658079/

相关文章:

c - 信号在 Linux 上如何工作(c)?

c++ - 为什么在已删除的默认构造函数旁边定义一个空拷贝构造函数会使空列表的值初始化失败?

c++ - C++隐式复制构造函数成员变量复制顺序

c - 当我传入一个非零值作为参数时,为什么这个函数打印 0?

c - 在 task_struct (sched.h) 中添加新变量破坏鼠标 LINUX 内核

c - 函数中的双指针取消引用

c - 即使函数带有参数,也会对函数原型(prototype)发出警告

c++ - 使用扩展类声明方法

c - 带标题的不完整类型

c++ - 是否允许在 std::declval<T> 上使用 decltype (函数本身,而不是调用它的结果)?