为什么没有参数的函数(与实际函数定义相比)会编译?


388

我刚刚遇到了某人的C代码,但对于为什么编译它感到困惑。我不明白两点。

首先,函数原型与实际函数定义相比没有参数。其次,函数定义中的参数没有类型。

#include <stdio.h>

int func();

int func(param)
{
    return param;
}

int main()
{
    int bla = func(10);    
    printf("%d", bla);
}

为什么这样做?我已经在几个编译器中对其进行了测试,并且工作正常。


77
是K&RC。我们在1980年代就编写了这样的代码,然后才有了完整的功能原型。
hughdbrown 2012年

6
gcc确实-Wstrict-prototypesint func()和发出警告int main():xc:3:警告:函数声明不是原型。你应该申报main()main(void)为好。
詹斯

3
@Jens为什么要编辑问题?您似乎错过了要点...
dlras2

1
我只是将隐式int明确了。那怎么错了呢?我相信重点是为什么int func();与兼容int func(arglist) { ... }
詹斯

3
@MatsPetersson这是错误的。C99 5.1.2.2.1明显与您的主张相矛盾,并声明no参数版本为int main(void)
詹斯2012年

Answers:


271

所有其他答案都是正确的,但仅是为了完成

以以下方式声明函数:

  return-type function-name(parameter-list,...) { body... }

return-type是函数返回的变量类型。不能是数组类型或函数类型。如果未给出,则假定为int

function-name函数的名称

parameter-list是函数采用的参数列表,以逗号分隔。如果未提供任何参数,则该函数将不包含任何参数,并且应使用空括号或关键字void定义该函数。如果参数列表中的变量之前没有变量类型,则假定为int。数组和函数不传递给函数,而是自动转换为指针。如果列表以省略号(,...)结尾,则没有设置的参数数量。注意:使用省略号时,头stdarg.h可用于访问参数。

再次为了完整性。从C11规范6:11:6(页面:179)

使用与空括号函数声明的(未原型格式参数类型说明符)是一个过时特征


2
“如果参数列表中的变量前面没有变量类型,则假定为int。” 我在您提供的链接中看到了此内容,但在任何标准的c89,c99中都找不到。您可以提供其他来源吗?
godaygo

“如果未给出任何参数,则该函数将不使用任何参数,并且应使用一组空括号来定义该函数”听起来与Tony The Lion的答案矛盾,但我刚刚了解到Tony The Lion的答案很难。
jakun

160

C func()表示您可以传递任意数量的参数。如果不需要参数,则必须声明为func(void)。您传递给函数的类型(如果未指定)默认为int


3
实际上,没有默认为int的单一类型。实际上,所有args都默认为int(甚至返回类型)。调用完全可以func(42,0x42);所有调用都必须使用两个args形式)。
詹斯(Jens)

1
在C语言中,未指定的类型默认为int是很常见的,例如声明变量时:unsigned x; 变量x是什么类型?事实证明它是未签名的int
bitek

4
我宁愿说隐式int 在过去常见。这当然不是任何时间更长,在C99它是从下除

2
可能值得注意的是,此答​​案适用于参数列表为空的函数原型,但不适用于参数列表为空的函数定义。
自闭症

@mnemonicflow不是“默认为int的未指定类型”;unsigned是与相同类型的另一个名称unsigned int
hobbs 2015年

58

int func();是从没有C标准的日子开始的过时函数声明,即K&R C的日子(1989年之前,第一个“ ANSI C”标准发布的年份)。

请记住,K&R C没有原型,关键字void尚未发明。您所能做的就是告诉编译器函数的返回类型。K&R C中的空参数列表表示“数量未指定但固定”的参数。固定表示每次调用函数时必须使用相同数量的args(这与可变参数函数(如printf可变参数每次调用的数量和类型可能不同)不同。

许多编译器会诊断这种构造。特别是gcc -Wstrict-prototypes会告诉您“函数声明不是原型”,因为它看起来像原型(特别是如果您被C ++毒害!),但事实并非如此。这是旧式的K&R C返回类型声明。

经验法则:切勿将空的参数列表声明留​​空,int func(void)具体而言。这会将K&R返回类型声明转换为正确的C89原型。编译器很高兴,开发人员很高兴,静态检查程序很高兴。但是,那些受C ++的^ W ^ Wong误导的人可能会畏缩,因为在尝试锻炼外语技能时,他们需要键入额外的字符:-)


1
请不要将此与C ++相关。这是常识是空的参数列表意味着没有参数,和世界的人都没有兴趣去对付由K&R的家伙大约40年前所犯的错误。但是委员会专家们一直将反对的选择拖到C99,C11中。
pfalcon,2014年

1
@pfalcon 常识是相当主观的。一个人的常识对其他人来说简直是疯狂。专业的C程序员知道,空的参数列表表示未指定但固定数量的参数。进行更改可能会破坏许多实现。责怪委员会避免无声的改变正在吠叫错误的树,恕我直言。
延斯2014年

53
  • 空的参数列表表示“任何参数”,因此定义没有错。
  • 假定缺少的类型为int

我会认为,任何通过此设置的构建都缺少配置的警告/错误级别,因此没有必要考虑实际代码。


30

它是K&R样式函数的声明和定义。从C99标准(ISO / IEC 9899:TC3)

6.7.5.3节函数声明符(包括原型)

标识符列表仅声明函数参数的标识符。作为该函数定义的一部分的函数声明器中的空列表表示该函数没有参数。不属于该函数定义的一部分的函数声明器中的空列表指定不提供有关参数数量或类型的信息。(如果两种功能类型均为“旧样式”,则不比较参数类型。)

6.11.6节函数声明符

使用带有空括号的函数声明符(不是原型格式的参数类型声明符)是过时的功能。

6.11.7节功能定义

将函数定义与单独的参数标识符和声明列表(不是原型格式的参数类型和标识符声明符)一起使用是一种过时的功能。

旧风格意味着K&R风格

例:

宣言: int old_style();

定义:

int old_style(a, b)
    int a; 
    int b;
{
     /* something to do */
}

16

C假定int是否在函数返回类型和参数列表中没有给出任何类型。仅对于遵循此规则的怪异事物是可能的。

函数定义如下所示。

int func(int param) { /* body */}

如果它是原型你写

int func(int param);

在原型中,您只能指定参数的类型。参数名称不是必需的。所以

int func(int);

另外,如果您未指定参数类型,但名称int被假定为类型。

int func(param);

如果您走得更远,跟随也可以。

func();

编译器假定int func()您在编写时func()。但是不要放在func()功能体内。那将是一个函数调用


3
空的参数列表与隐式int类型无关。int func()不是的隐式形式int func(int)
John Bartholomew 2012年

11

如@Krishnabhadra所述,以前其他用户的所有回复均具有正确的解释,我只想对一些要点进行更详细的分析。

在ANSI-C中的Old-C中,“ 无类型的形式参数 ”采用工作寄存器或指令深度功能(影子寄存器或指令累积周期)的尺寸,在8位MPU中将是int16,在16位中MPU等等将是int16等等,在64位体系结构可以选择编译诸如-m32之类的选项的情况下。

尽管在高层似乎更简单的实现,但是对于传递多个参数,程序员在控制维度数据类型步骤中的工作变得更加艰巨。

在其他情况下,对于某些微处理器体系结构,ANSI编译器进行了定制,利用一些旧功能来优化代码的使用,从而迫使这些“无类型的形式参数”的位置在工作寄存器内部或外部工作,今天您将获得与“ volatile”和“ register”的用法几乎相同。

但应注意,最现代的编译器在两种类型的参数声明之间没有任何区别。

在Linux下使用gcc进行编译的示例:

main.c

main2.c

main3.c  
在任何情况下,在本地对原型的声明都是没有用的,因为没有参数的调用就不会被引用。如果将系统与“无类型的形式参数”一起使用,则对于外部调用,请继续生成声明性原型数据类型。

像这样:

int myfunc(int param);

5
我已经尽力优化图像,因为图像的字节大小是最小的。我认为图片可以快速查看生成的代码中缺少差异的地方。我一直在测试代码,生成有关其要求的真实文档,而不仅仅是对问题进行理论化。但并非所有人都以相同的方式看待世界。非常抱歉,我尝试向社区提供更多信息而困扰您如此之多。
RTOSkit 2012年

1
我敢肯定,未指定的返回类型始终为int,通常为工作寄存器大小或16位,以较小者为准。
2013年

@supercat +1完美演绎,对了!这正是我上面所讨论的,默认情况下为指定体系结构设计的编译器,总是反映出所讨论的CPU / MPU工作寄存器的大小,因此在嵌入式环境中,对实时操作系统,可以进入编译器层(stdint.h)或可移植层,以使可移植的其他操作系统具有相同的操作系统,并可以通过对齐CPU / MPU特定类型(int,long,long long等)获得通用系统类型为u8,u16,u32。
RTOSkit

@supercat如果没有操作系统或特定编译器提供的控件类型,则需要花些时间在开发时间上,并且要使所有“未类型化的分配”与您的应用程序设计保持一致,然后才能惊奇地发现“ 16bit int”而不是“ 32bit int”。
RTOSkit

@RTOSkit:您的答案表明8位处理器上的默认类型为Int8。我确实记得有几种PICmicro品牌架构的C ish编译器,就是这种情况,但是我认为,远非类似于C标准的任何东西都不允许int那种不能同时容纳该范围内所有值的类型-从32767到+32767(不是必需的-32768注释),并且还能够保存所有char值(这意味着如果char是16位,则必须对其进行签名或int必须更大)。
2013年

5

关于参数类型,这里已经有正确的答案,但是如果您想从编译器中听到它,则可以尝试添加一些标志(无论如何,标志几乎总是一个好主意)。

使用gcc foo.c -Wextra我编译您的程序:

foo.c: In function func’:
foo.c:5:5: warning: type of param defaults to int [-Wmissing-parameter-type]

奇怪的-Wextra是,clang它并没有抓住它(它-Wmissing-parameter-type由于某种原因而无法识别,也许是因为上述历史原因),但是却-pedantic做到了:

foo.c:5:10: warning: parameter 'param' was not declared, 
defaulting to type 'int' [-pedantic]
int func(param)
         ^
1 warning generated.

对于原型问题,上面再说一遍,int func()是指任意参数,除非您明确定义它,否则int func(void)将给您带来预期的错误:

foo.c: In function func’:
foo.c:6:1: error: number of arguments doesnt match prototype
foo.c:3:5: error: prototype declaration
foo.c: In function main’:
foo.c:12:5: error: too many arguments to function func
foo.c:5:5: note: declared here

clang作为:

foo.c:5:5: error: conflicting types for 'func'
int func(param)
    ^
foo.c:3:5: note: previous declaration is here
int func(void);
    ^
foo.c:12:20: error: too many arguments to function call, expected 0, have 1
    int bla = func(10);
              ~~~~ ^~
foo.c:3:1: note: 'func' declared here
int func(void);
^
2 errors generated.

3

如果函数声明没有参数,即为空,那么它将使用未指定数量的参数。如果要使其不带任何参数,则将其更改为:

int func(void);

1
“如果函数原型没有参数”,那不是函数原型,只是一个函数声明。
effeffe 2012年

@effeffe修复了该问题。我没有意识到这个问题会引起很多注意;)
PP

3
“函数原型”不只是“函数声明”的替代表达式(可能是老式的)吗?
乔治

@ Giorgio IMO,两个都是正确的。“功能原型”应与“功能定义”匹配。我可能认为effeffe表示问题中的声明,而我的意思是我的答案。
PP

@ Giorgio否,函数声明由返回类型值,标识符和可选的参数列表组成;函数原型是带有参数列表的函数声明。
effeffe 2012年

0

这就是为什么我通常建议人们使用以下方法编译代码:

cc -Wmissing-variable-declarations -Wstrict-variable-declarations -Wold-style-definition

这些标志强制执行以下几项操作:

  • -Wmissing-variable-declarations:如果不先获取原型,就无法声明非静态函数。这使得头文件中的原型更有可能与实际定义相匹配。或者,它强制您将static关键字添加到不需要公开可见的函数中。
  • -Wstrict-variable-declarations:原型必须正确列出参数。
  • -Wold-style-definition:函数定义本身也必须正确列出参数。

默认情况下,许多开放源代码项目中也使用这些标志。例如,当在Makefile中以WARNS = 6进行构建时,FreeBSD启用了这些标志。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.