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

Posted

技术标签:

【中文标题】为啥没有参数的函数(与实际函数定义相比)编译?【英文标题】:Why does a function with no parameters (compared to the actual function definition) compile?为什么没有参数的函数(与实际函数定义相比)编译? 【发布时间】:2012-12-06 16:51:22 【问题描述】:

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

    函数原型与实际函数定义相比没有参数。

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


#include <stdio.h>

int func();

int func(param)

    return param;


int main()

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

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

【问题讨论】:

这是 K&R C。我们在 1980 年代编写这样的代码,那时还没有完整的功能原型。 gcc 对于int func()int main() 都使用-Wstrict-prototypes 发出警告:x.c:3: 警告:函数声明不是原型。您也应该将main() 声明为main(void) @Jens 你为什么编辑这个问题?你好像没抓住重点…… 我只是将隐式 int 显式化了。这怎么会错过重点?我相信重点是为什么int func();int func(arglist) ... 兼容。 @MatsPetersson 这是错误的。 C99 5.1.2.2.1 明确与您的主张相矛盾,并声明无参数版本为int main(void) 【参考方案1】:

所有其他答案都是正确的,但仅适用于completion

函数的声明方式如下:

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

return-type 是函数返回的变量类型。这不能是数组类型或函数类型。 如果没有给出,那么 int 假设

function-name 是函数的名称。

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

为了完整起见。 From C11 specification 6:11:6(第179页)

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

【讨论】:

“如果参数列表中的变量前面没有变量类型,则假定为 int。” 我在你提供的中看到了这一点链接,但我在任何标准的 c89、c99 中都找不到它...你能提供另一个来源吗? “如果没有给出参数,则函数不接受任何参数,并且应该用一组空括号定义”听起来与 Tony The Lion's answer 矛盾,但我刚刚了解到 Tony The Lion 的答案是正确的艰难的路。【参考方案2】:

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

【讨论】:

实际上没有单一类型默认为 int。事实上,所有 args 都默认为 int(甚至返回类型)。调用func(42,0x42); 完全没问题(其中 all 调用必须使用两个 args 形式)。 在 C 中,未指定的类型默认为 int 是很常见的,例如当你声明一个变量时: unsigned x;变量 x 是什么类型?原来它的 unsigned int 我宁愿说隐式 int 在过去很常​​见。它肯定不再存在,并且在 C99 中它已从 C 中删除。 值得注意的是,这个答案适用于具有空参数列表的函数原型,但不适用具有空参数列表的函数定义。 @mnemonicflow 不是“默认为 int 的未指定类型”; unsigned 只是与 unsigned int 相同类型的另一个名称。【参考方案3】:

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

请记住,K&R C 中没有原型,关键字 void 尚未发明。你所能做的就是告诉编译器一个函数的返回类型。 K&R C 中的空参数列表表示“未指定但固定”数量的参数。固定意味着您必须每次调用具有相同个参数的函数(而不是像printf这样的可变参数函数,其中数量和类型可以变化每次调用)。

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

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

【讨论】:

请不要将此与 C++ 联系起来。这是常识,空参数列表意味着没有参数,世界上的人们对处理大约40年前K&R家伙所犯的错误不感兴趣。但是委员会的专家们一直在将反自然的选择拖到 C99、C11 中。 @pfalcon 常识是相当主观的。对一个人来说是常识,对其他人来说是明显的精神错乱。专业的 C 程序员知道空参数列表表示未指定但固定数量的参数。改变它可能会破坏许多实现。恕我直言,指责委员会避免无声的改变是在找错树。【参考方案4】: 空参数列表表示“任何参数”,所以定义没有错。 假定缺少的类型为int

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

【讨论】:

【参考方案5】:

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

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

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

第 6.11.6 节函数声明符

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

第 6.11.7 节函数定义

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

old style表示K&R style

例子:

声明:int old_style();

定义:

int old_style(a, b)
    int a; 
    int b;

     /* something to do */

【讨论】:

【参考方案6】:

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

函数定义如下所示。

int func(int param)  /* body */

如果它是你写的原型

int func(int param);

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

int func(int);

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

int func(param);

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

func();

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

【讨论】:

空参数列表与隐式 int 类型无关。 int func() 不是 int func(int) 的隐式形式。【参考方案7】:

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

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

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

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

但应该注意的是,最现代的编译器, 两种类型的参数声明不做任何区分。

linux下gcc编译示例:

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

像这样:

int myfunc(int param);

【讨论】:

我不厌其烦地优化图像,因为图像的字节大小是可能的最小值。我认为图片可以快速查看生成的代码中缺乏差异。我一直在测试代码,生成它声称的真实文档,而不仅仅是对问题进行理论化。但并非所有人都以相同的方式看待世界。非常抱歉,我试图向社区提供更多信息给您带来了如此多的困扰。 我很确定未指定的返回类型始终是int,通常是工作寄存器大小或 16 位,以较小者为准。 @supercat +1 完美演绎,你是对的!这正是我上面讨论的内容,默认情况下为指定架构设计的编译器始终反映相关 CPU / MPU 的工作寄存器的大小,因此在嵌入式环境中,有各种重新键入策略实时操作系统,编译器层 (stdint.h) 或可移植层,这使得在许多其他架构中可移植相同的操作系统,并通过对齐 CPU / MPU 特定类型(int、long、long long 等)获得使用通用系统类型 u8、u16、u32。 @supercat 如果没有操作系统或特定编译器提供的控制类型,您需要在开发时稍加注意,并且让所有“无类型分配”与您的应用程序设计保持一致,在发现“16bit int”而不是“32bit int”之前会感到惊讶。 @RTOSkit:您的回答表明 8 位处理器上的默认类型是 Int8。我确实记得有几个用于 PICmicro 品牌架构的 C-ish 编译器,但我不认为任何与 C 标准相近的东西都允许int 类型不能同时保存所有值范围 -32767 到 +32767(注意 -32768 不是必需的)并且还能够保存所有 char 值(这意味着如果 char 是 16 位,则必须对其进行签名或 int 必须更大)。 【参考方案8】:

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

使用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.

【讨论】:

【参考方案9】:

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

int func(void);

【讨论】:

"如果函数原型没有参数" 那不是函数原型,只是函数声明。 @effeffe 解决了这个问题。我没有意识到这个问题会引起很多关注;) “函数原型”不只是“函数声明”的替代(可能是老式)表达式吗? @Giorgio IMO,两者都是正确的。 “函数原型”应该匹配“函数定义”。我可能认为 effeffe 意味着问题中的声明,而我的意思是我的答案。 @Giorgio no,函数声明由返回类型值、标识符和一个可选参数列表组成;函数原型是带有参数列表的函数声明。【参考方案10】:

这就是为什么我通常建议人们编译他们的代码:

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

这些标志强制执行几件事:

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

在许多开源项目中默认使用这些标志。例如,当在 Makefile 中使用 WARNS=6 进行构建时,FreeBSD 会启用这些标志。

【讨论】:

【参考方案11】:

在旧式声明器中

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

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

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

在新式声明中

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

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

来源:- K&R 书

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

【讨论】:

以上是关于为啥没有参数的函数(与实际函数定义相比)编译?的主要内容,如果未能解决你的问题,请参考以下文章

C语言问题,宏定义中的参数为啥要定义,不是主函数中的参数直接替换吗?

为啥当我有两个函数时编译器没有显示错误,一个将基类作为参数,一个将派生类作为参数?

C++中,为啥函数参数不够也可以调用?而且函数模板定义中没有提供默认值。

函数参数的内存分配

当一个函数无返回值时,函数的类型应定义为啥

声明与定义的区别