为什么 MISRA:2012 中需要函数原型?

Why function prototypes are they required in MISRA:2012?

我想知道为什么 MISRA:2012 需要函数原型。在下面的示例中,这两个原型并不是必需的。

#include <stdio.h>
#include <stdlib.h>

// >>> Truly useless in my opinion
void display(void);
int main(void);
// <<<

void display(void) {
    printf("Hello World!\n");
}

int main() {
    display();
    return EXIT_SUCCESS;
}

我可以在 here 等 SO 上阅读的基本原理对我来说不是很清楚。例如,如果 main 在声明之前尝试访问 display,编译器或静态分析器将引发错误:函数显示在声明之前使用。

换句话说,为这个 MISRA 规则创建一个偏差是个好主意吗?

如果不声明函数,任何函数调用都会为每个参数调用default argument promotions,因为认为函数具有C89标准的语义。

Case 1:

考虑调用 f(5),其中函数的参数类型为 double。 f 的代码会将 5 视为双精度数,而默认的算术提升将仅传递一个整数。

丢失包含声明的 header 文件可能会使您的代码传递整数,实际上函数会将它们视为导致段错误的指针。这是一个具体的例子:

Case 2:

假设您要使用函数 strtok 并且不在声明中包含 header string.h -- char *strtok(char *str, const char *delim)

现在你错误地考虑分隔符 'A' 而不是 "A"。因此,如果您忘记了签名但请记住参数的含义,如果您不包含 header 代码将在没有警告的情况下编译,当然来自 strtok 的代码将考虑您的 char 'A' (转换为整数 (=95)) 作为实参。代码认为它是一个指向字符串的指针,并将尝试从位置 95 以段错误结束访问该指针。

Case 3:

这是在某些体系结构上出现段错误的另一个典型代码示例——即使您没有犯任何错误,它仍然会出现段错误。

char *subtoken;
subtoken = strtok(str, delim, &saveptr);

在这种情况下,函数 strtok(缺少来自 string.h 的声明)被认为是 return 和 int,因此来自 [=22= 的隐式转换] 制作。如果int用32位表示,指针用64位表示,显然subtoken的值是错误的,会产生seg fault。

void display(void); 是函数前向声明。它有原型格式。

如 link 帖子中所示,函数 prototype 是一个函数声明,其中指定了所有参数的类型。如果没有参数,那么参数列表必须是(void)(没有参数)而不是()(任何参数)。

确切规则 8.2 说:

Rule 8.2 Function types shall be in prototype form with named parameters

提供的基本原理(阅读它,非常好)提到这是为了避免未指定所有参数的旧 K&R 和 C90 程序。 C99 在某种程度上仍然允许这样做,只要函数声明中的参数类型不与函数定义中的参数类型冲突。

本质上,该规则旨在禁止这些类型的功能:

void func1 (x)  // K&R style
int x;
{}

void func2(x)  // sloppy style
{}

所有参数(如果有)都必须指定类型和名称。

但是,我在 MISRA-C 中没有找到任何要求您为每个函数编写函数声明的内容。这意味着无论有无函数声明,您的示例代码都将符合此 MISRA 规则。


尽管正如我在之前的回答中提到的那样,编写没有函数声明(以原型格式)的 .c 文件是草率的做法。如果您的函数需要按特定顺序调用,则应通过程序设计、函数命名和comments/documentation 使其显而易见。不是按照它们恰好在 .c 文件中声明的顺序。

在 .c 文件中声明函数的源代码行与该函数的 behavior/use 之间不应紧密耦合。

相反,函数应该按照逻辑上有意义的顺序定义。编写 .c 文件的一种常见方法是将所有 public 函数(它们的函数声明在 .h 文件中)保留在 .c 文件的顶部。然后让内部功能(那些 static/internal linkage 的)坐在底部。该模型需要所有内部函数的函数声明。另一种选择是将所有内部函数放在顶部,将 public 函数放在底部。只要你是一致的,任何一个都可以。

最重要的是,如果 .c 文件中的函数定义被重新排序,它不应该破坏程序或导致编译器错误。确保这一点的最简单方法是始终为程序中的每个函数提供函数声明。

请注意,文件顶部的函数声明根本不是 "truly useless",因为它们提供了 C 文件中存在的所有函数的快速摘要。这是一种编写自文档化代码的方法。


请注意,作为一种特殊情况,C 标准不允许 main() 的原型。

另外请注意,规则 8.7 和 8.8 不允许您在没有 static 的情况下使用 void display(void),因为该功能仅在一个翻译单元中使用。