当您不包含和使用 malloc 时,我们会收到隐式声明警告。
“警告:内置函数‘malloc’的隐式声明不兼容”
此警告是由于编译器假定 malloc 定义为 int malloc(size) 而它是void* malloc(size)
.
但它是怎么知道的void* malloc(size)
?我们没有在头文件中包含任何内容。那么如何将它与不包括在内的东西进行比较。
在那之后,我的代码是如何工作的?它如何找到正确的 malloc 定义并使用它?
扫描函数定义是否有任何顺序?
当您不包含和使用 malloc 时,我们会收到隐式声明警告。
“警告:内置函数‘malloc’的隐式声明不兼容”
此警告是由于编译器假定 malloc 定义为 int malloc(size) 而它是void* malloc(size)
.
但它是怎么知道的void* malloc(size)
?我们没有在头文件中包含任何内容。那么如何将它与不包括在内的东西进行比较。
在那之后,我的代码是如何工作的?它如何找到正确的 malloc 定义并使用它?
扫描函数定义是否有任何顺序?
当你调用一个f
从未定义过的函数时,会发生这样的隐式声明:
int f();
请注意,您仍然可以将参数传递给f
,因为它没有被声明int f(void);
(这是为了向后兼容 K&R C)。
因此,编译器不“知道” malloc 接收大小参数,事实上你可以传递你想要的东西,它不在乎。
因此,代码有效的事实纯粹是运气。在 malloc 的情况下,如果代码有效,它只是意味着整数的大小与指针的大小相同 - 不多也不少 - 因此,您仍然可以调用 malloc 并将其结果分配给指针,因为没有位被修剪/丢失。
真正的功能是在编译发生后的链接阶段找到的。此时,您的代码已经使用错误的函数原型编译。当然,如果链接器在其路径中的任何位置都找不到该函数,则会报告错误并中止一切。
对于 malloc 和其他标准库函数,可能有内置函数来提高性能。gcc 甚至还有一个选项可以禁用内置函数(-fno-builtin
或-fno-builtin-function
)。从 gcc 手册页:
GCC 通常会生成特殊代码来更有效地处理某些内置函数;例如,对“alloca”的调用可能成为直接调整堆栈的单个指令,对“memcpy”的调用可能成为内联复制循环。生成的代码通常更小更快,但由于函数调用不再显示为这样,因此您无法在这些调用上设置断点,也无法通过链接不同的库来更改函数的行为。此外,当一个函数被识别为内置函数时,GCC 可能会使用有关该函数的信息来警告调用该函数的问题,或者生成更高效的代码,即使生成的代码仍然包含对该函数的调用.
因此,在 malloc 的特定情况下,这就是编译器“知道”其通常签名的方式。尝试用 gcc 编译这段代码:
int main(void) {
char *a = malloc(12, 13, 14, 15);
return 0;
}
您将看到它将因编译错误而中止:
test.c:3: error: too many arguments to function `malloc'
如果您使用 option -fnobuiltin
,错误就会消失并且警告会有所不同:
test.c:3: warning: implicit declaration of function `malloc'
每次使用以前未定义的常规函数时,都会收到相同的警告,因为现在编译器忽略了他对这些函数的“了解”。此示例使用 gcc,但其他编译器也会有类似的行为。
但它是怎么知道的
void* malloc(size)
?我们没有在头文件中包含任何内容。那么如何将它与不包括在内的东西进行比较。
大多数现代编译器都有一个内置的(即在编译器中硬编码)常用标准库函数列表,因此即使没有声明(显式或隐式)或调用函数,编译器也知道声明应该是什么。
这并不意味着将使用正确的声明(因为隐式声明规则会覆盖编译器的明显知识),但至少您知道自己做错了什么。
这些内置函数的确切目的是,如果您忘记包含头文件,编译器可以警告您。(还有其他目的,例如通过了解内置函数的语义/实现来执行内在优化的机会,但这些不适用于此处。)
您收到此特定警告的原因是此特定编译器malloc
中的内置函数(如警告所述)。此函数接受编译器的特殊处理。编译器对如何声明这个函数有内在的了解,所以当隐式声明与正确的声明相矛盾时,会立即发出警告。
在一般情况下,对于普通(非内置)函数,当编译器实际上发现同一函数的第一个显式声明时(假设两者之间不匹配),隐式声明的函数会出现类似的衰减。在这种情况下,警告将在稍后发出:不是在调用时,而是在显式声明时。这就是它与普通函数一起工作的方式。
但是对于像编译器这样的标准(内置)函数,malloc
它允许自己有点“作弊”。
如果您没有正确声明它(在源代码中使用显式声明或包含正确的标头),它将找不到正确的定义。它对在声明之前调用的任何函数使用默认的隐式声明。隐式声明包括函数返回的假设int
。
然后它将默认声明与您调用函数的方式进行比较。它注意到您将结果分配给指针变量而不是整数,因此它警告您这些不兼容。