【问题标题】:Why function prototypes are they required in MISRA:2012?为什么 MISRA:2012 需要函数原型?
【发布时间】:2018-03-16 18:41:32
【问题描述】:

我想知道为什么 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 规则创建一个偏差是个好主意吗?

【问题讨论】:

  • 我认为:1)这意味着您可以在不破坏代码的情况下对函数进行排序,2)它避免了对隐式声明的任何回归。
  • 恕我直言,这完全没有实际意义,现代编译器会警告您隐式函数声明。但是您有动机:使用“坏”编译器或错误的编译器设置,您可能会通过重新排序函数来引入未被注意到的错误。始终拥有原型可以防止这种情况发生。
  • 我的建议:启用所有警告 -Wall、-Wextra。现在您可以忘记静态分析工具中的一半规则。
  • 那段代码还是很糟糕的,因为display() 没有被声明为static,如果意图是函数是它所在文件的本地函数,它当然应该是这样。
  • @nowox 函数本身充当原型。 直到代码被重新排序并且它没有。 如果 foo 只在同一个翻译单元中使用,则必须在使用前声明它。 这不是真的。并非所有 C 编译器都需要原型才能调用函数。旧版 C 代码甚至可能无法在较新的标准下编译。再说一遍:想想传递给没有原型的函数的参数会发生什么。

标签: c misra


【解决方案1】:

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

Case 1:

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

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

Case 2:

假设你想使用函数strtok,并且你没有在声明中包含标题string.h -- char *strtok(char *str, const char *delim)

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

Case 3:

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

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

在这种情况下,函数strtok(缺少来自string.h 的声明)被认为返回一个int,因此从int-&gt;char* 进行了隐式转换。如果 int 用 32 位表示,指针用 64 位表示,显然 subtoken 的值是错误的,会产生 seg 错误。

【讨论】:

    【解决方案2】:

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

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

    确切的规则 8.2 说:

    规则 8.2 函数类型应为带有命名参数的原型形式

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

    基本上,该规则旨在禁止这些功能:

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

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

    然而,我在 MISRA-C 中没有发现需要您为每个函数编写函数声明的内容。这意味着无论有没有函数声明,您的示例代码都将符合此 MISRA 规则。


    尽管正如我在之前的回答中提到的,编写没有函数声明(原型格式)的 .c 文件是一种草率的做法。如果你的函数需要按一定的顺序调用,应该通过程序设计、函数命名和 cmets/documentation 来明确。不是按照它们恰好在 .c 文件中声明的顺序。

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

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

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

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


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

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

    【讨论】:

    • 有趣的是我没有得到 8.7 和 8.8,而是 8.4:[MISRAC2012-Rule-8.4]:Definition of externally-linked main()' has no compatible declaration.. However if I add int main(void);`我可以得到摆脱这个错误。你知道为什么吗?
    • @nowox - MISRA 于 2017 年 6 月发布了技术勘误,明确为 main() 规则 8.4 添加了一个例外,因此建议您从工具供应商处获取更新。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-07
    • 2011-07-11
    • 2012-01-17
    • 1970-01-01
    相关资源
    最近更新 更多