编译器给出 - 警告 C4013:'getche' 未定义;假设 extern 返回 int

Compiler gives - warning C4013: 'getche' undefined; assuming extern returning int

我正在使用 Visual studio 进行代码开发以及何时使用函数

getche()

编译器给了我这个警告

 warning C4013: 'getche' undefined; assuming extern returning int

但是 getche() 函数按预期工作,为什么编译器会显示这样的警告,以及如何消除这个警告?

正如您的编译器告诉您的那样,getche 尚未定义。

通常,您会包含一个 header(例如 #include <conio.h>),它定义的函数有点类似于:

int getche(void);

C 语言有一个规则,允许使用以前没有定义的函数。假定这些函数采用它们传递的确切参数类型(none,因此 void)和 return int。由于此功能(这是邪恶的™)以及 getche 确实由于您的链接器设置而链接进来的事实,您的程序实际上可以正常工作。

请注意 getche is deprecated and you should use _getche

我想补充一点:更正此类错误并非易事,因为缺少声明可能会导致更多错误。

以简单的调用为例foo(4)。假设没有关于此调用如何工作的更多信息。编译器能够从这样的调用中读取什么?

  1. 参数的数量(很明显)。
  2. 参数的类型(4 通常扩展为 4 字节值,4LL 在 64 位机器 (gcc) 上扩展为 8 字节值)。
  3. 参数压入堆栈的顺序(调用约定 - 大多数情况下是 C 约定,但这取决于您的编译器设置)。

编译器无法从该调用中读取什么内容:

  1. 什么样的值调用returns.

没错。因为 foo 可能定义在另一个模块中,甚至在系统库中。没有声明的符号 foo 对编译器来说是完全未知的。因为它只是编译你的代码,但它不负责你的符号的链接(我说符号是因为它可以包含变量 函数)。

现在, 编译器必须做什么?

  1. 每次调用都需要生成指令。根据调用约定,这些指令可能会有很大差异。例如,对于 cdeclcaller 必须将参数压入堆栈,并且在每次调用函数之前进行。对于 stdcall被调用者 必须清理堆栈。

  2. 它需要知道参数是什么类型。好吧,实际上它并不关心类型,而是关心对象占据的大小。一个 20 字节的结构对象在堆栈上需要 20 个字节,不是吗?而且每个参数都需要放在上面(按值调用)

  3. 并且需要在栈上为函数的return值预留足够的内存。因为:无论函数是什么 return 都存储在该内存中。如果函数 return 是一个 20 字节的结构对象,它就需要 20 字节。到目前为止,还不错。

现在,除了 return 值 之外的任何值都可以从调用中读取 - return 值必须由声明提供。如果未提供此声明,编译器将只在堆栈上为 return 值保留 sizeof(int)(大多数情况下为 4 个字节),并将其余部分留给链接器。

链接器看到符号 foo(4 bytes) 并将开始寻找 foo(4 bytes) 的任何对应定义。如果它发现(比如,在你的另一个模块中,或者在你系统的 libc 中,或者作为系统调用包装器),那么链接器是内容并且它将创建可执行文件。

一切都很好,不是吗?嗯,不,不是。

例如,在 GNU 系统上提供了一个名为 memmem 的函数,它在包含 string.h 之前定义 _GNU_SOURCE 时被激活。如果您在包含该函数之前碰巧没有定义 _GNU_SOURCE,则 memmem 的声明将被跳过,编译器将无法确定 return 该函数具有和将要具有的内容自动假定为 4 个字节。当您在 32 位系统上时,这是 "OK" - 但如果您的程序被编译为 64 位可执行文件,并且您传递给 memmem 的 haystack 是一个指向 [=24= 上方的 64 位指针], 指针的高 32 位被擦除(就好像你写了 return (pointer&0xFFFFFFFFULL);)。而这个新指针可以指向我所说的"bad nirvana"对应于"good nirvana"。 "good nirvana" 为NULL,大家可以看出这个指针不应该被解引用。对指向 "bad nirvana" 的指针执行此操作更加困难。

链接器对您的 memmem 调用不会有任何问题,因为我们的系统库仍然知道该函数。不求而得,用则背叛。

所以请记住:即使您的编译器不需要声明,您仍然希望提供一个。