代码之家  ›  专栏  ›  技术社区  ›  eca2ed291a2f572f66f4a5fcf57511

该标准是否对无返回类型的函数有任何规范性措辞?

  •  3
  • eca2ed291a2f572f66f4a5fcf57511  · 技术社区  · 10 年前

    尽管许多编译器都可以容忍没有返回类型的函数,但N1570的标准似乎没有对其进行规范性的表述。为什么这是可能的?

    1 回复  |  直到 10 年前
        1
  •  0
  •   Jonathan Leffler    9 年前

    在原始C标准(C89、C90)中,第6.7.1节涵盖了功能定义。在C99和C11标准中,第6.9.1节涵盖了功能定义。两者之间有一个微小但关键的区别。

    ISO/IEC 9899:1990

    §6.7.1功能定义

    语法

    函数定义:
    声明说明符 选择 声明者声明列表 选择 复合语句

    ISO/IEC 9899:1999和ISO/IEC 9999:2011

    §6.9.1功能定义

    语法

    1. 函数定义:
    声明说明符声明器声明列表 选择 复合语句

    下标 选择 中缺少 声明说明符 在标准的较新版本中。

    这就是控制返回类型的任何标准的“函数定义”部分中的全部内容。

    接下来的两段在标准的所有三个版本中都很常见(尽管我省略了一个带有不同数字的脚注)。

    2函数定义中声明的标识符(即函数的名称)应具有函数类型,如函数定义的声明器部分所规定。

    3函数的返回类型应为 void 或数组类型以外的完整对象类型。

    这个 声明说明符 可以省略类型(因此它可以声明 static 或 extern 没有类型)。在C90中,这是合法的;在C99和C11中,这是不允许的。与此相关的章节为C90中的§6.5.2和C99和C11中的§6.7.2,用于类型说明符。

    ISO/IEC 9899:1990

    类型说明符

    约束条件
    每个类型说明符列表应为以下集合之一(当 一条线上有多个集合);类型说明符可能以任何顺序出现 与其他声明说明符混合。

    ISO/IEC 9899:1999和ISO/IEC 9999:2011

    6.7.2类型规范

    约束条件
    每个声明中的声明说明符中应至少给出一个类型说明符, 以及在每个结构声明和类型名称中的说明符限定符列表中。每个列表 类型说明符应为以下集合之一(当存在 一条线上不止一组);类型说明符可能以任何顺序出现 与其他声明说明符混合。

    C99/C11文本中的第一句排除了不包含类型的声明说明符的可能性,而C90在这一点上是沉默的,因此声明说明器可以省略类型。

    执行

    直到最近(GCC5),GNU C编译器使用C90标准作为其默认操作模式,因此它对允许没有显式返回类型的函数定义松懈,因为C90标准对此松懈。考虑到1989年或1990年C代码的状态,这种松懈是必要的,因为当时许多现有代码都不包含返回类型。如果C90标准对这一点很严格,它就会失败。事实上,它非常成功。

    从GCC 5.1开始,GNU C编译器默认使用C11标准作为其默认操作模式,因此它会生成关于未定义返回类型的函数的警告。如果您愿意,可以将这些警告转换为错误。让我们重申一下:你 应该 将这些警告转换为错误,然后修复代码,以便不再生成警告。

    考虑一下存储在文件中的邋遢(按现代标准)代码 old-style.c :

    function(int x)
    {
        return 2 * x * x + 3 * x - 7;
    }
    

    使用GCC 6.3.0在运行macOS Sierra 10.12.4的Mac上编译,我得到:

    $ gcc -c old-style.c
    old-style.c:1:1: warning: return type defaults to ‘int’ [-Wimplicit-int]
     function(int x)
     ^~~~~~~~
    $ gcc -std=c11 -c old-style.c
    old-style.c:1:1: warning: return type defaults to ‘int’ [-Wimplicit-int]
     function(int x)
     ^~~~~~~~
    $ gcc -std=c99 -c old-style.c
    old-style.c:1:1: warning: return type defaults to ‘int’ [-Wimplicit-int]
     function(int x)
     ^~~~~~~~
    $ gcc -std=c90 -c old-style.c
    $
    

    因此,只有指定了C90标准,我才能在默认选项下获得“干净”(无警告)编译。

    正在添加 -Werror 或 -Werror=implict-int 将这些警告转换为错误:

    $ gcc -Werror -std=c11 -c old-style.c
    old-style.c:1:1: error: return type defaults to ‘int’ [-Werror=implicit-int]
     function(int x)
     ^~~~~~~~
    cc1: all warnings being treated as errors
    $ gcc -Werror=implicit-int -std=c11 -c old-style.c
    old-style.c:1:1: error: return type defaults to ‘int’ [-Werror=implicit-int]
     function(int x)
     ^~~~~~~~
    cc1: some warnings being treated as errors
    $
    

    不同的是,平原 -Werror公司 制造 全部的 警告转换为错误,但 -Werror=implicit-int 将该警告转化为错误(其他警告仍为警告)。

    我自己的编码标准要求在定义或使用非静态函数之前,必须使用完整的原型声明它。静态函数在使用之前可以在没有预先原型的情况下定义。此外,任何非静态函数声明都应该在头中;函数不是静态的唯一原因是,它将被另一个源文件使用,并且头是必要的,以便编译器检查函数定义位置和使用位置之间的一致性。 因此,我的默认编译选项是:

    $ gcc -Wall -Wextra -Werror -Wstrict-prototypes -Wmissing-prototypes \
    >     -Wold-style-declaration -Wold-style-definition -std=c11 -c old-style.c
    old-style.c:1:1: error: return type defaults to ‘int’ [-Werror=implicit-int]
     function(int x)
     ^~~~~~~~
    old-style.c:1:1: error: no previous prototype for ‘function’ [-Werror=missing-prototypes]
    cc1: all warnings being treated as errors
    $
    

    GCC的某些版本不支持 -Wold-style-xxx 选项,而Clang不喜欢 -Wold-style-declaration 选项,所以我经常省略。

    在中固定 new-style.c 相当简单:

    extern int function(int x);     /* Should be in a header */
    
    int function(int x)
    {
        return 2 * x * x + 3 * x - 7;
    }
    

    而且它在严格的编译选项下编译,一声不响:

    $ gcc -Wall -Wextra -Werror -Wstrict-prototypes -Wmissing-prototypes \
    >     -Wold-style-declaration -Wold-style-definition -std=c11 -c new-style.c
    $
    

    其他编译器的规则与GCC类似,不同的版本可能会做不同的事情,默认情况下会抱怨不同的问题,并需要不同的选项来生成您需要的诊断。使用多个编译器(最好是在多个平台上)来检查代码的工作情况总是很有帮助的。

    推荐文章