#include<stdio.h>
int f();
int main()
{
f(1);
f(1,2);
f(1,2,3);
}
f(int i,int j,int k)
{
printf("%d %d %d",i,j,k);
}
它正常运行(没有任何错误)...您能解释一下它是如何执行的吗? f(1)和f(1,2)如何与f(int,int,int)相关联?
#include<stdio.h>
int f();
int main()
{
f(1);
f(1,2);
f(1,2,3);
}
f(int i,int j,int k)
{
printf("%d %d %d",i,j,k);
}
它正常运行(没有任何错误)...您能解释一下它是如何执行的吗? f(1)和f(1,2)如何与f(int,int,int)相关联?
你对"error"这个术语的定义与我不同 :-) 当你调用你的f
函数前两次时会打印出什么?我的输出是:
1 -1216175936 134513787
1 2 134513787
1 2 3
对于我的三个函数调用。
你看到的是C语言早期人们在函数调用中随意而自由的一个遗留问题。
所有发生的事情就是你正在调用一个函数f
,它从堆栈中打印出三个值(即使你只给了一个或两个值)。当你没有提供足够的值时,你的程序很可能会使用已有的值,这通常会导致在读取时出现数据问题,并在写入时导致灾难性故障。
这是完全可编译的,但非常不明智的C代码。我指的是“未定义行为”的字面意思(特别是指C99:“如果表示所调用函数的表达式具有不包括原型的类型,则如果参数的数量不等于参数的数量,则行为未定义”)。
你应该提供完整的函数原型,例如:
void f(int,int,int);
为确保编译器能够检测到这个问题,在可变参数函数中使用省略号 (...
)。
顺便说一下,通常在背后发生的是调用函数从以下堆栈开始:
12345678
11111111
并将两个值推入堆栈(例如),以便它最终看起来像:
12345678
11111111
2
1
当被调用的函数使用栈上的前三个值时(因为它需要这些值),它发现栈中有1
、2
和11111111
。
它执行所需的操作,然后返回,调用函数会清除掉那两个值(这被称为调用者清理策略)。如果有人试图使用被调用者清理策略进行此操作,那么将会遇到麻烦 :-) 尽管在C语言中这相当少见,因为这样做会使可变参数函数(如printf
)变得有些困难。
setjmp
和longjmp
也是如此——这并不一定是一个好主意 :-) 无论如何,我添加了一点关于为什么它很可能有效的内容。 - paxdiablo*((int*)NULL) = 37;
是完全有效的代码,但并不正确,会导致未定义的行为。声明一个函数int f()
是标准的,并声明了一个返回int
类型和接受未知类型和数量参数的函数,但未知并不意味着你可以随意调用它。你只是告诉编译器停止打扰你,因为你知道你在做什么。 - David Rodríguez - dribeasf(1);
f(2,2);
f(3,2,3);
}int f(int i,int j,int k)
{
if ( i == 1 ) printf("%d\n", i);
if ( i == 2 ) printf("%d %d\n", i, j);
if ( i == 3 ) printf("%d %d %d\n", i, j, k);
}` . 现在,你可以专注于它为什么能够工作。OP的示例代码只是“马虎”,但他表达了自己的观点。 - ShinTakezouint f();
这段代码告诉编译器:“f
是一个接受固定数量参数并返回int
的函数”。然后你尝试用一个、两个和三个参数来调用它 - C编译器在概念上是单遍的(预处理后),所以此时编译器没有可用的信息与你争论。
你实际上实现的f()
接受三个int
参数,所以只提供一个或两个参数的调用会导致未定义的行为 - 这是一种错误,意味着编译器不需要给出错误消息,并且运行程序时可能发生任何事情。
f
这种方式。 - ShinTakezou
int f();
在C语言中,这声明了一个可以接受可变数量参数的函数,也就是在C++中等价于以下内容:
int f(...);
int f();
int f(void);
这将导致编译器发出警告。
请注意:这里还涉及到C链接器的一个怪异之处... C链接器不会在调用函数时验证传递的参数,并且只是链接到具有相同名称的第一个公共符号。因此,在main中使用f()是允许的,因为声明了int f()
。但是,在调用点上,链接器会在链接时间绑定函数f(int, int, int)。希望这有些意义(如果没有,请告诉我)
f();
,编译器在调用该函数时可以自由使用被调用者清理堆栈的调用约定,例如 x86 上的 stdcall
。但对于真正的可变参数函数,则不行。 - caf它可以正常运行,因为int f()
意味着其他答案已经解释过的:它表示参数数量未指定。这意味着您可以使用您想要的参数数量来调用它(也可以超过3个),而编译器不会报错。
它之所以在“内部”工作的原因是因为参数被推入堆栈,然后在f
函数中“从”堆栈中访问。如果您传递0个参数,则函数的、j、k“对应”于函数PoV中的垃圾值。尽管如此,您仍然可以访问它们的值。如果您传递1个参数,则其中一个i,j,k
访问该值,其他值则获取垃圾值。以此类推。
请注意,如果参数以其他方式传递,相同的推理也适用,但无论如何这些约定都在使用中。这些约定的另一个重要方面是被调用者不负责调整堆栈;这由知道实际推送了多少参数的调用者负责。如果不是这样,f
的定义可能会暗示它需要“调整”堆栈以“释放”三个整数,这将导致某种崩溃。
你所编写的代码对于当前标准来说是可以的(在gcc编译时即使加了-std=c99 -pedantic
,也没有警告;有一个警告,但只是关于定义前缺少int的问题),尽管许多人认为这是"过时特性"并且很讨厌。当然,在示例代码中你的用法并没有展示出任何有用性,相反使用原型更能帮助消除错误!(但我仍然更喜欢C而不是Ada)
补充
更加 "有用" 的用法不会引起 "未定义行为" 问题,可以是:
#include<stdio.h>
int f();
int main()
{
f(1);
f(2,2);
f(3,2,3);
}
int f(int i,int j,int k)
{
if ( i == 1 ) printf("%d\n", i);
if ( i == 2 ) printf("%d %d\n", i, j);
if ( i == 3 ) printf("%d %d %d\n", i, j, k);
}
f(1);
和f(1, 2);
这两个函数调用,那么这段代码就没问题了。 - caf当你使用g++编译器编译相同的程序时,会看到以下错误 -
g++ program.c
program.c: In function `int main()':
program.c:2: error: too many arguments to function `int f()'
program.c:6: error: at this point in file
program.c:2: error: too many arguments to function `int f()'
program.c:7: error: at this point in file
program.c:2: error: too many arguments to function `int f()'
program.c:8: error: at this point in file
program.c: At global scope:
program.c:12: error: ISO C++ forbids declaration of `f' with no type
使用带有 -std=c99 选项的 gcc 只会产生警告
使用与 g++ 默认设置相同的标准编译同一程序,会出现以下信息:
gcc program.c -std=c++98
cc1: warning: command line option "-std=c++98" is valid for C++/ObjC++ but not for C
我的答案是C编译器遵循的标准与C++遵循的标准不同,前者没有后者那么严格。
int f();
声明一个返回类型为int
的函数。这个声明不包含函数所需参数的任何信息。函数的定义为
f(int i,int j,int k)
{
printf("%d %d %d",i,j,k);
}
现在已知该函数需要三个int
参数。如果您使用与定义不同的参数调用函数,则不会得到编译时错误,而是运行时错误(或者如果您不喜欢错误的负面含义:“未定义的行为”)。C编译器标准并不强制捕获这些不一致之处。
为了防止这些错误,您应该使用适当的函数原型,例如
f(int,int,int); //in your case
f(void); //if you have no parameters