395

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

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

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


#include <stdio.h>

int func();

int func(param)
{
    return param;
}

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

为什么这行得通?我已经在几个编译器中对其进行了测试,并且效果很好。

4

11 回答 11

274

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

函数以下列方式声明:

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

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

function-name是函数的名称。

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

再次为了完整起见。来自 C11 规范 6:11:6(第 179 页)

使用带空括号的函数声明符(不是原型格式参数类型声明符)是一个过时的特性

于 2012-12-19T10:56:28.537 回答
161

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

于 2012-12-19T10:50:19.113 回答
59

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^Wfond 误导的人可能会畏缩,因为他们在尝试锻炼外语技能时需要输入额外的字符:-)

于 2012-12-19T10:55:49.290 回答
53
  • 空参数列表表示“任何参数”,因此定义没有错。
  • 缺失的类型假定为int.

我会认为任何通过它的构建都缺少配置的警告/错误级别,但允许实际代码是没有意义的。

于 2012-12-19T10:51:51.110 回答
30

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

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

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

6.11.6 节函数声明器

使用带空括号的函数声明符(不是原型格式参数类型声明符)是一个过时的特性。

6.11.7 节函数定义

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

其中old style就是K&R style

例子:

宣言:int old_style();

定义:

int old_style(a, b)
    int a; 
    int b;
{
     /* something to do */
}
于 2012-12-19T11:07:33.007 回答
15

C 假设int函数返回类型和参数列表中是否没有给出类型。仅对于此规则,以下奇怪的事情是可能的。

函数定义如下所示。

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

如果它是您编写的原型

int func(int param);

在原型中你只能指定参数的类型。参数的名称不是强制性的。所以

int func(int);

此外,如果您没有指定参数类型,但名称int被假定为类型。

int func(param);

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

func();

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

于 2012-12-19T20:49:45.183 回答
11

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

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

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

在其他情况下,对于某些微处理器架构,ANSI 编译器定制,利用这些旧功能来优化代码的使用,强制这些“无类型形式参数”的位置在工作寄存器内部或外部工作,今天你得到使用“volatile”和“register”几乎相同。

但需要注意的是,最现代的编译器,并没有对这两种类型的参数声明做任何区分。

linux下用gcc编译的例子:

主程序

main2.c

main3.c  
无论如何,在本地声明原型是没有用的,因为没有参数的任何调用都不会引用这个原型。如果您使用带有“无类型形式参数”的系统,对于外部调用,请继续生成声明性原型数据类型。

像这样:

int myfunc(int param);
于 2012-12-19T23:46:58.277 回答
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 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.
于 2012-12-19T17:43:32.130 回答
3

如果函数声明没有参数,即为空,则它采用未指定数量的参数。如果您想让它不带参数,请将其更改为:

int func(void);
于 2012-12-19T10:51:21.050 回答
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 启用了这些标志。

于 2013-02-12T21:38:48.320 回答
0

在旧式声明器中,

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

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

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

在新式声明中

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

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

资料来源:- K&R 书

我希望它消除了你的疑问..

于 2021-04-22T03:57:30.890 回答