为什么 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)
,因为该功能仅在一个翻译单元中使用。
我想知道为什么 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)
,因为该功能仅在一个翻译单元中使用。