[英]Warning: implicit declaration of function 'calloc'
#include<stdio.h>
int *arr;
int main()
{
arr = calloc(1, sizeof(int));
free(arr);
return 0;
}
据我了解,出现此警告是因为我没有在 header 中声明 function (在这种情况下,我应该包含stdlib.h
)。 我的问题是:
为什么GCC不报错? 因为据我所知, calloc
位于stdlib.h
,但我没有将它包含在我的程序中。 为什么我的程序仍然知道什么是calloc
?
我们应该对警告闭上眼睛吗? 因为即使不包含stdlib.h
,我的程序也能正常运行。
据我所知,出现此警告是因为我没有在 header 中声明 function(在这种情况下我应该包含
<stdlib.h>
)。
你是对的。 编译器抱怨您正在调用 function,但它没有看到它的声明。 包括<stdlib.h>
确实为 function calloc
提供了正确的声明。 请注意,您也可以通过添加以下行自己提供声明: void *calloc(size_t, size_t);
. 然而,建议使用标准包含文件来获取库函数的准确声明。
为什么GCC不报错?
因为编译器很宽松,可以编译在 C 标准发布之前编写的旧程序。 在 scope 中调用没有声明或定义的函数过去不会出错。编译器只会根据提供的 arguments 的类型推断原型。 在您的情况下,原型被推断为int calloc(int, size_t)
,这显然是不正确的并且会导致未定义的行为。 为避免此类问题,编译器会发出有关未声明calloc
的警告。
为什么我的程序仍然知道什么是
calloc
?
事实并非如此,原型是从调用语句中推断出来的,这个猜测不成立。 事实上,编译器还应该抱怨int
返回值在存储到arr
时被隐式转换为int *
。 如果int *
和int
在目标系统上有不同的表示(如在 64 位系统上的情况),则arr
的值将无效并且free(arr)
肯定也会有未定义的行为。 编译器生成可执行文件,因为 function calloc
位于 C 库中,它隐式链接到所有 C 程序。 C 程序在链接时不检查参数和返回类型。
我们应该对警告闭上眼睛吗?
当然不。 您应该使用gcc -Wall -Wextra -Werror
启用所有警告来编译您的程序,并且 gcc 将考虑所有警告,如致命错误,并拒绝生成可执行文件,直到它们被更正。 这将为您节省许多小时的调试时间。 遗憾的是,这种行为不是默认行为。
即使不包含
<stdlib.h>
我的程序也能正常运行。
好吧,它似乎可以在您的系统上运行,但在我的系统上却失败了(假设我删除了阻止gcc
生成可执行文件的默认编译器选项)。 该程序具有未定义的行为。 不要依赖机会。
这是一个正确的版本:
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr = calloc(1, sizeof(*arr)); /* use object type for consistency */
printf("pointer value is %p\n", (void *)arr);
free(arr);
return 0;
}
function 不在stdlib.h
中,它位于同一个共享库 object, libc.so
中,因此您的 linker 可以毫无问题地找到符号。 如果您尝试调用虚构的 function,您也不会收到编译器错误,而您的 linker 会抱怨无法解析符号。
你在技术上可以,但你绝对不应该。 如果你不这样做,你就不会收到有关参数错误的警告。 Linker 不会(也不能)检查您是否提供了正确数量和类型的 arguments。
例子:
// a.c
int asdf(int b){
return b+1;
}
// main.c
int main(void) {
asdf();
return 0;
}
如果你现在编译: gcc main.c a.c
你只会得到一个警告,而如果你包含了一个 header 你会得到一个错误。 这是BAD ,因为它可能导致未定义的行为,从而导致意外崩溃、memory 损坏、安全问题等。
你应该总是给你的编译器一个公平的机会来帮助你。
编辑:澄清一下,忽略是不好的
calloc
不在stdlib.h
中。 stdlib.h
中没有任何内容。 .h
中什么都没有(或者什么都不应该)。
stdlib.h
中的内容只是(我的意思是关于calloc
)
extern void *calloc(size_t nmemb, size_t size);
calloc
在libc
中。 那就是你的程序在调用它时会找到它的地方。
严格来说,您不需要任何其他东西(我的意思是,从执行的角度来看)就能调用 function。
编译所有.c
代码来完成它们的任务。 libc
早就编译好了。 在链接时(对于 static 库,动态指向它们)然后在运行时,加载所有编译代码中的所有函数。 所以你的main
from your code 和calloc
会在那个时候找到对方。
问题不在那里。
问题是为了编译你的 main,编译器需要知道应该如何调用calloc
。
它需要知道它来检查语法错误。 否则,您可以将 3 arguments 或仅一个传递给calloc
,编译器将无法知道这是不正确的。 您可以传递错误类型的 arguments,同样。
它还需要知道应该将多少字节作为参数推送,甚至应该如何传递 arguments。
参见例如这两个代码
一.c
#include <stdio.h>
void printInt(int a, int b){
printf("ints %d %d\n", a, b);
}
void printFloat(float a, float b){
printf("floats %f %f\n", a, b);
}
void printDouble(double a, double b){
printf("doubles %f %f\n", a, b);
}
二.c
#include <stdio.h>
int main(void) {
printInt(1,2);
printInt(1.0, 2.0);
printFloat(1,2);
printFloat(1.0,2.0);
printDouble(1,2);
printDouble(1.0,2.0);
}
编译它们(取决于你的编译器)
gcc -std=gnu99 -o main one.c two.c # There is an implicit -lc here including libc, that contains printf
在我的电脑上打印
ints 1 2
ints 1034078176 -2098396512
floats 0.000000 0.000000
floats 0.000000 0.000000
doubles 0.000000 0.000000
doubles 1.000000 2.000000
看到它仅适用于使用整数调用的printInt
和使用双打调用的printDouble
。 它无法将1.0
转换为 int 来调用printInt
,或者相反,无法将1
转换为 double 来调用printDouble
。 对于printFloat
,它在所有情况下都会失败,因为编译器错误地假设了要推送的 arguments 的大小。
但除此之外,这 3 个函数被调用。 丢失的不是 function 的代码。 这是编译器在调用它们时正确调用它们的能力。
只需添加two.c
声明
extern void printInt(int, int);
extern void printFloat(float, float);
extern void printDouble(double, double);
(或者创建一个包含这些的one.h
,并在two.c
中#include "one.h"
,它会导致相同的结果)
现在 output 符合预期
ints 1 2
ints 1 2
floats 1.000000 2.000000
floats 1.000000 2.000000
doubles 1.000000 2.000000
doubles 1.000000 2.000000
我什至还没有开始类型声明。
#include
并不意味着提供库和其中的函数。 您在链接时所做的,将.o
和-lsomelib
添加到喜欢的命令行(或使用其他方式,具体取决于您的编译器)。
#include
用于提供编译器需要知道如何调用这些函数的无代码声明。
- 为什么GCC不报错? 因为据我了解,calloc 位于 stdlib.h,但我没有将它包含在我的程序中。 为什么我的程序仍然知道什么是 calloc?
为了与遗留代码兼容,这不是错误,因此无法中止编译,因此您会收到警告。 在旧的遗留 C 代码中,如果它返回一个int
结果(如果您没有为函数指定原型,则假定),您可以使用没有声明的 function。 旧的 K&R 代码在 C98 中仍然有效(我不能保证以后的标准版本,但至少我已经测试了 K&R 代码并且它编译时最多有一些可以忽略的警告)所以它必须被编译成工作代码(但仍然可能是无效代码,因为生成的调用 calloc 的代码会考虑返回一个int
而calloc
确实返回一个void *
指针类型,在 64 位架构中是 64 位)
试试这段代码,然后看看:)
main(argc, argv)
char **argv;
{
int i;
char *sep = "";
for (i = 0; i < argc; i++) {
printf("%s[%s]", sep, *argv++);
sep = ", ";
}
puts("");
}
上面的代码在运行 unix v7(发布前刚刚测试过)的 pdp11 和最新版本 gcc 中编译没有任何问题。 是向您显示stdout
上的argv
命令参数列表。
$ make pru$$
Make: Don't know how to make pru31. Stop.
[tty2]lcu@pdp-11 $ cc -o pru$$ pru$$.c
[tty2]lcu@pdp-11 $ pru$$ a b c d e f
[pru31], [a], [b], [c], [d], [e], [f]
[tty2]lcu@pdp-11 $
This is pdp11/45 UNIX(tm) V7
(C) 1978 AT&T Bell Laboratories. All rights reserved.
Restricted rights: Use, duplication, or disclosure
is subject to restrictions stated in your contract with
Western Electric Company, Inc.
login:
- 我们应该对警告闭上眼睛吗? 因为即使不包含 stdlib.h,我的程序也能正常运行。
我认为你永远不应该在任何警告时闭上眼睛。 如果您只是#include <stdlib.h>
,编译器将在没有警告的情况下正确编译源代码。 关于警告的重要一点是您应该在继续之前阅读并理解它们(以及忽略它们的后果)。 减弱只是一个建议消息,它不会中断编译,因为这可能是编译器运行的预期目的(只是编译旧的遗留代码,这将产生一个有效的程序,但是很久以前写的)
在上面的例子中,你最好做正确的#include <stdio.h>
,因为如果你不这样做,你的代码就会出错。 它将假定calloc()
function 返回一个int
(在 64 位体系结构中大小不同)并且该值可能是一个截断的指针(一个指针,其中的某些部分已通过转换为int
被截断) 并且不会工作(但是最近发生了这种情况,使用 64 位架构,在 32 位架构上很好,不正确但有效)所以,你可以服从或不服从,但你是你自己的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.