6

当我一开始没有包含任何头文件时,编译器如何知道 sleep 函数甚至 printf 函数的原型?

此外,如果我指定sleep(1,1,"xyz")或任意数量的参数,编译器仍会编译它。但奇怪的是 gcc 能够在链接时找到这个函数的定义,我不明白这怎么可能,因为实际sleep()的函数只接受一个参数,但我们的程序提到了三个参数。

/********************************/
int main()
{
 short int i;
 for(i = 0; i<5; i++)
 {
    printf("%d",i);`print("code sample");`
    sleep(1);
 }
 return 0;
}
4

7 回答 7

10

由于缺少更具体的原型,编译器将假定函数返回 int 并采用您提供的任何数量的参数。

根据 CPU 体系结构,参数可以在寄存器中传递(例如,MIPS 上的 a0 到 a3)或通过将它们压入堆栈,就像在原始 x86 调用约定中一样。在任何一种情况下,传递额外的参数都是无害的。被调用的函数不会使用传入的寄存器,也不会引用堆栈上的额外参数,但不会发生任何不好的事情。

传入更少的参数会更成问题。被调用的函数将使用恰好在适当的寄存器或堆栈位置中的任何垃圾,并且可能会出现问题。

于 2008-09-16T02:34:28.230 回答
5

在经典 C 中,调用函数不需要原型。编译器将推断该函数返回一个 int 并采用未知数量的参数。这可能适用于某些体系结构,但如果函数返回非 int 的内容(如结构)或有任何参数转换,它将失败。

在您的示例中,可以看到 sleep 并且编译器假定一个原型

int sleep();

请注意,参数列表为空。在 C 中,这与 void 不同。这实际上意味着“未知”。如果您正在编写 K&R C 代码,您可能会通过代码获得未知参数,例如

int sleep(t)
int t;
{
   /* do something with t */
}

这很危险,特别是在一些嵌入式芯片上,未原型函数的参数传递方式与原型函数不同。

注意:链接不需要原型。通常,链接器会自动与 Linux 上的 glibc 等 C 运行时库链接。您对 sleep 的使用与实现它的代码之间的关联发生在源代码处理很久之后的链接时间。

我建议您使用编译器的功能来要求原型以避免此类问题。对于 GCC,它是 -Wstrict-prototypes 命令行参数。在 CodeWarrior 工具中,它是 C/C++ 编译器面板中的“需要原型”标志。

于 2008-09-16T03:23:03.370 回答
2

C 将猜测 int 的未知类型。所以,它可能认为 sleep 有这个原型:

int sleep(int);

至于提供多个参数和链接......我不确定。这确实让我感到惊讶。如果这真的有效,那么在运行时发生了什么?

于 2008-09-16T02:22:05.640 回答
2

其他答案涵盖了可能的机制(未指定编译器的所有猜测)。

您遇到的问题是您的编译器和链接器尚未设置为启用所有可能的错误和警告。对于任何新项目,(实际上)没有理由不这样做。 为遗留项目提供更多借口——但应努力启用尽可能多的

于 2008-09-16T12:31:34.457 回答
2

这与称为“K & R C”和“ANSI C”的东西有关。在良好的旧 K 和 RC 中,如果未声明某些内容,则假定为 int。因此,任何看起来像函数调用但未声明为函数的东西都会根据实际调用自动获取“int”的返回值和参数类型。

然而人们后来发现这有时会非常糟糕。所以几个编译器添加了警告。C++ 犯了这个错误。我认为 gcc 有一些标志( -ansic 或 -pedantic? ),这使这种情况成为错误。

所以,简而言之,这是历史包袱。

于 2008-09-16T03:56:12.480 回答
1

在非玩具示例中,另一个文件可能包含您错过的文件。查看预处理器的输出是查看最终编译结果的好方法。

于 2008-09-16T03:25:33.380 回答
1

取决于编译器,但使用 gcc(例如,因为这是您提到的那个),一些标准(C 和 POSIX)函数具有内置的“编译器内在函数”。这意味着您的编译器附带的编译器库(在本例中为 libgcc)包含该函数的实现。编译器将允许隐式声明(即,使用不带标头的函数),并且链接器将在编译器库中找到实现,因为您可能将编译器用作链接器前端。

尝试使用“-c”标志编译您的对象(仅编译,无链接),然后使用链接器直接链接它们。你会发现你得到了你期望的链接器错误。

或者,gcc 支持禁用内部函数的选项:-fno-builtin或用于精细控制,-fno-builtin-function. 如果您正在执行诸如构建自制内核或某种其他类型的实体应用程序之类的操作,那么还有一些可能有用的选项。

于 2008-09-16T02:24:25.667 回答