编译器给出 - 警告 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
确实由于您的链接器设置而链接进来的事实,您的程序实际上可以正常工作。
我想补充一点:更正此类错误并非易事,因为缺少声明可能会导致更多错误。
以简单的调用为例foo(4)
。假设没有关于此调用如何工作的更多信息。编译器能够从这样的调用中读取什么?
- 参数的数量(很明显)。
- 参数的类型(4 通常扩展为 4 字节值,4LL 在 64 位机器 (gcc) 上扩展为 8 字节值)。
- 参数压入堆栈的顺序(调用约定 - 大多数情况下是 C 约定,但这取决于您的编译器设置)。
编译器无法从该调用中读取什么内容:
- 什么样的值调用returns.
没错。因为 foo
可能定义在另一个模块中,甚至在系统库中。没有声明的符号 foo
对编译器来说是完全未知的。因为它只是编译你的代码,但它不负责你的符号的链接(我说符号是因为它可以包含变量 和 函数)。
现在, 编译器必须做什么?
每次调用都需要生成指令。根据调用约定,这些指令可能会有很大差异。例如,对于 cdecl
,caller 必须将参数压入堆栈,并且在每次调用函数之前进行。对于 stdcall
,被调用者 必须清理堆栈。
它需要知道参数是什么类型。好吧,实际上它并不关心类型,而是关心对象占据的大小。一个 20 字节的结构对象在堆栈上需要 20 个字节,不是吗?而且每个参数都需要放在上面(按值调用)
并且需要在栈上为函数的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
调用不会有任何问题,因为我们的系统库仍然知道该函数。不求而得,用则背叛。
所以请记住:即使您的编译器不需要声明,您仍然希望提供一个。
我正在使用 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
确实由于您的链接器设置而链接进来的事实,您的程序实际上可以正常工作。
我想补充一点:更正此类错误并非易事,因为缺少声明可能会导致更多错误。
以简单的调用为例foo(4)
。假设没有关于此调用如何工作的更多信息。编译器能够从这样的调用中读取什么?
- 参数的数量(很明显)。
- 参数的类型(4 通常扩展为 4 字节值,4LL 在 64 位机器 (gcc) 上扩展为 8 字节值)。
- 参数压入堆栈的顺序(调用约定 - 大多数情况下是 C 约定,但这取决于您的编译器设置)。
编译器无法从该调用中读取什么内容:
- 什么样的值调用returns.
没错。因为 foo
可能定义在另一个模块中,甚至在系统库中。没有声明的符号 foo
对编译器来说是完全未知的。因为它只是编译你的代码,但它不负责你的符号的链接(我说符号是因为它可以包含变量 和 函数)。
现在, 编译器必须做什么?
每次调用都需要生成指令。根据调用约定,这些指令可能会有很大差异。例如,对于
cdecl
,caller 必须将参数压入堆栈,并且在每次调用函数之前进行。对于stdcall
,被调用者 必须清理堆栈。它需要知道参数是什么类型。好吧,实际上它并不关心类型,而是关心对象占据的大小。一个 20 字节的结构对象在堆栈上需要 20 个字节,不是吗?而且每个参数都需要放在上面(按值调用)
并且需要在栈上为函数的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
调用不会有任何问题,因为我们的系统库仍然知道该函数。不求而得,用则背叛。
所以请记住:即使您的编译器不需要声明,您仍然希望提供一个。