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

我刚刚遇到了一些人的C代码,我很困惑为什么它要编译。有两点我不明白。

  1. 与实际的函数定义相比,函数原型没有参数。

  2. 函数定义中的形参没有类型。


#include <stdio.h>


int func();


int func(param)
{
return param;
}


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

为什么这样做? 我已经在几个编译器中测试了它,它工作得很好

70384 次浏览

在C中func()意味着你可以传递任意数量的参数。如果你不想要参数,那么你必须声明为func(void)。传递给函数的类型,如果没有指定,默认为int

如果函数声明没有形参,即为空,则它将接受不指定数量的参数。如果你想让它不带参数,那么将其更改为:

int func(void);
  • 空参数列表表示“任何参数”,因此定义没有错。
  • 缺失的类型被假定为int

我认为任何通过这一点的构建都缺乏配置的警告/错误级别,尽管如此,允许实际代码没有任何意义。

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

记住有在K& rc没有原型,关键字void还没有发明。你所能做的就是告诉编译器函数的返回类型。在K&R C中,空形参表表示“一个未指定但固定的”参数数量。Fixed意味着你每次调用函数时必须使用相同数量的参数(而不是可变函数,如printf,每次调用时参数的数量和类型都可以变化)。

许多编译器会诊断这个构造;特别是gcc -Wstrict-prototypes会告诉你“函数声明不是一个原型”,这是正确的,因为它看起来像一个原型(特别是如果你被c++毒害了!),但不是。这是一个老式的K& rc返回类型声明。

永远不要让空参数列表声明为空,具体使用int func(void)。 这将K&R返回类型声明转换为适当的C89原型。编译器很高兴,开发人员很高兴,静态检查器也很高兴。那些被c++的^W^Wfond误导的人可能会畏缩,因为当他们尝试练习外语技能时,他们需要输入额外的字符:-)

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

函数的声明方式如下:

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

返回类型是函数返回的变量类型。这不能是数组类型或函数类型。如果没有给出,则int 假定> < /强劲。< / p >

函数名是函数的名称。

参数列表是函数以逗号分隔的参数列表。如果没有给出参数,则函数 不取任何值,应该用空集定义 括号或关键字void。如果前面没有变量类型 对于参数列表中的变量,则假设int 。数组和 函数不是传递给函数,而是自动转换 指针。如果列表以省略号(,…)结束,则 没有固定数量的参数。注意:标头stdarg.h可以是 用于在使用省略号时访问参数

为了完整起见。从C11规范6:11:6 (page: 179)

使用带有空括号的函数声明器(不是 是过时的 < /强>。< / p >

它是K& R风格的函数声明和定义。来自C99标准(ISO/IEC 9899:TC3)

函数声明器(包括原型)

标识符列表只声明函数参数的标识符。一个空 作为该函数定义的一部分的函数声明器中的列表指定 函数没有参数。函数声明器中的空列表,它不是类的一部分 类的数量或类型的信息 提供参数。(如果两个函数类型都是"old style",则参数类型不进行比较)

函数声明器

使用带有空括号的函数声明符(而不是prototype-format参数) type声明器)是即将过时的特性。

函数定义

使用带有单独参数标识符和声明列表的函数定义 (不是prototype-format参数类型和标识符声明器)是即将过时的特性。

旧样式的意思是K& R样式

例子:

声明:int old_style();

定义:

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

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

编译你的程序使用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 doesn’t 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.

如果函数返回类型和参数列表上没有给出类型,C假设int。只有遵循这条规则,才有可能发生奇怪的事情。

函数定义是这样的。

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

如果它是一个原型,你写

int func(int param);

在原型中,你只能指定参数的类型。参数名称为非必选项。所以

int func(int);

同样,如果你没有指定参数类型,但名称int被假设为类型。

int func(param);

如果你走得更远,跟着也行。

func();

编译器在编写func()时假定int func()。但是不要把func()放在函数体中。这是一个函数调用

正如@Krishnabhadra所说,其他用户之前的所有回答都有正确的解释,我只是想对一些观点做更详细的分析。

在Old-C中,如在ANSI-C中,“无类型形式参数”,取你的工作寄存器或指令深度能力(阴影寄存器或指令累积周期)的维度,在一个8位MPU中,将是一个int16,在一个16位MPU中,因此将是一个int16,等等,在这种情况下,64位架构可以选择编译选项,如:-m32。

虽然它在高层的实现看起来更简单, 对于传递多个参数,程序员在控制维度数据类型步骤中的工作,变得更加苛刻

在其他情况下,对于一些微处理器架构,ANSI编译器自定义,利用一些旧的特性来优化代码的使用,迫使这些“无类型的形式参数”的位置在工作寄存器内部或外部工作,今天你几乎可以使用“volatile”和“register”。

但是应该指出的是,最现代的编译器,

.

在linux下使用gcc编译的例子:

main.c .

main2.c

main3.c .  
在任何情况下,原型的语句在本地是没有用的,因为没有参数的调用对这个原型的引用将是疏忽的。 如果在外部调用中使用“untyped formal parameter”系统,则继续生成声明性的原型数据类型

是这样的:

int myfunc(int param);

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

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

这些标志执行了一些事情:

  • -Wmissing-variable-declarations:在没有得到原型的情况下,不可能声明一个非静态函数。这使得头文件中的原型更有可能与实际定义相匹配。或者,它强制将static关键字添加到不需要公开可见的函数中。
  • -Wstrict-variable-declarations:原型必须正确地列出参数。
  • - word -style-definition:函数定义本身也必须正确地列出参数。

在许多开源项目中也默认使用这些标志。例如,FreeBSD在Makefile中使用warnings =6构建时启用了这些标志。

在旧式的声明器中,

标识符列表必须不存在,除非 声明器用于函数定义的头部 (Par.A.10.1)。参数类型信息不为 由声明提供。例如,声明

.
int f(), *fpi(), (*pfi)();

声明一个函数f返回一个整数,一个函数fpi返回一个整数指针,>和一个指针pfi返回一个整数函数。这些都没有指定参数类型>它们是老式的。

在new-style声明中

int strcpy(char *dest, const char *source), rand(void);

strcpy是a 函数返回int,有两个参数,第一个是字符 指针,第二个是指向常量字符的指针

来源:- K&R书

我希望这消除了你的疑虑。