我想知道为什么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这样的基本原理对我来说不是很清楚。例如,如果main
在声明之前尝试访问display
,则编译器或静态分析器将引发错误:声明之前使用的函数显示。
换句话说,为这个MISRA规则创建偏差是个好主意吗?
答案 0 :(得分:3)
void display(void);
是一个函数转发声明。它有原型格式。
如发布的链接所示,函数 prototype 是一个函数声明,其中包含指定的所有参数的类型。如果没有参数,那么参数列表必须是(void)
(无参数)而不是()
(任何参数)。
确切的规则8.2说:
规则8.2函数类型应为 prototype 形式,并带有命名参数
提供的基本原理(阅读它,它非常好)提到这是为了避免旧的K&amp; R和C90程序,而不是所有参数都指定。只要函数声明中的参数类型不与函数定义中的参数类型冲突,C99在某种程度上仍允许这样做。
基本上,该规则试图禁止这些功能:
void func1 (x) // K&R style
int x;
{}
void func2(x) // sloppy style
{}
所有参数(如果有)必须指定类型和名称。
然而,我在MISRA-C中找不到任何要求你为每个函数编写函数声明的内容。这意味着您的示例代码在有或没有函数声明的情况下符合此MISRA规则。
虽然正如我在之前的回答中提到的那样,编写没有函数声明的.c文件(原型格式)是一种邋practice的做法。如果需要按特定顺序调用函数,则应通过程序设计,函数命名和注释/文档使其显而易见。不是它们碰巧在.c文件中声明的顺序。
在.c文件中声明函数的源代码行与该函数的行为/用途之间不应该存在紧密耦合。
相反,应该按照逻辑上有意义的顺序定义函数。编写.c文件的一种常用方法是将所有公共函数保存在.c文件顶部的.h文件中。然后让内部函数(具有static
/内部链接的函数)位于底部。该模型需要所有内部函数的函数声明。另一种选择是将所有内部函数放在最顶层,将公共函数放在底部。只要你保持一致,要么就好了。
最重要的是如果.c文件中的函数定义被重新排序,它不应该破坏程序或导致编译器错误。确保这一点的最简单方法是始终为程序中的每个函数提供函数声明。
请注意,文件顶部的函数声明根本不是“真正无用”,因为它们提供了C文件中存在的所有函数的快速摘要。这是一种编写自我记录代码的方法。
请注意,作为特殊情况,C标准不允许使用main()原型。
请注意,此外,规则8.7和8.8禁止您使用void display(void)
而不使用static
,因为该功能仅用于一个翻译单元。
答案 1 :(得分:2)
如果你没有声明这个函数,任何函数调用都会为每个参数调用default argument promotions
,因为它被认为该函数具有C89标准的语义。
Case 1:
考虑调用f(5),其中函数的参数是double
类型。 f的代码将5视为double,而默认的arith促销仅传递整数。
包含声明的头文件丢失可能会使代码传递整数,实际上是将它们视为导致seg错误的指针的函数。这是一个具体的例子:
Case 2:
假设您要使用strtok
函数,并且未将标题string.h
包含在声明中 - char *strtok(char *str, const char *delim)
现在你错误地考虑了分隔符'A'而不是“A”。因此,如果您忘记了签名但请记住参数的含义,如果您不包含标题,代码将在没有警告的情况下进行编译,当然strtok的代码会考虑您的char'A'(以整数转换) (= 95))作为实际论点。代码认为它是一个指向字符串的指针,并将尝试从完成segfault的位置95访问指针。
Case 3:
这是另一个代码段的典型代码,即使你没有犯任何错误,它仍然是段错误。
char *subtoken;
subtoken = strtok(str, delim, &saveptr);
在这种情况下,函数strtok
(缺少来自string.h
的声明)被视为返回int
,因此从int->char*
进行隐式转换。如果int表示为32位且指针位于64位上,则显然suboken的值将是错误的并且将产生seg错误。