在原始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类似,不同的版本可能会做不同的事情,默认情况下会抱怨不同的问题,并需要不同的选项来生成您需要的诊断。使用多个编译器(最好是在多个平台上)来检查代码的工作情况总是很有帮助的。