警告:函数'calloc'的隐式声明

  • 本文关键字:声明 calloc 函数 警告 c
  • 更新时间 :
  • 英文 :

#include<stdio.h>
int *arr;
int main()
{
arr = calloc(1, sizeof(int));
free(arr);
return 0;
}

据我了解,出现此警告是因为我没有在标头中声明函数(在这种情况下,我应该包含stdlib.h)。我的问题是:

  1. 为什么 GCC 没有给出错误?因为据我了解,calloc位于stdlib.h,但我没有将其包含在我的程序中。为什么我的程序仍然知道什么是calloc

  2. 我们应该对警告闭上眼睛吗?因为我的程序即使不包含stdlib.h也能很好地运行。

据我了解,出现此警告是因为我没有在标头中声明该函数(在这种情况下,我应该包含<stdlib.h>)。

你是对的。编译器抱怨您正在调用一个尚未看到声明的函数。包括<stdlib.h>确实为函数calloc提供了正确的声明。 请注意,您也可以通过添加以下行来自己提供声明:void *calloc(size_t, size_t);.但是,建议使用标准包含文件来获取库函数的确切声明。

为什么 GCC 没有给出错误?

因为编译器是宽松的,以便编译在C标准发布之前编写的旧程序。过去,在范围内没有声明或定义的情况下调用函数不是错误。编译器只会从提供的参数类型推断原型。在您的情况下,原型被推断为int calloc(int, size_t),这显然是不正确的,并且会导致未定义的行为。 为了避免此类问题,编译器会发出有关未声明calloc的警告。

为什么我的程序仍然知道什么是calloc

它没有,原型是从调用语句推断出来的,这个猜测不足。 事实上,编译器还应该抱怨int返回值在存储到arr时被隐式转换为int *。 如果int *int在目标系统上具有不同的表示形式(如在 64 位系统上的情况),则arr的值将无效,并且free(arr)肯定也会具有未定义的行为。 编译器生成可执行文件,因为函数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 %pn", (void *)arr);
free(arr);
return 0;
}

calloc不在stdlib.h中。什么都没有stdlib.h.没有任何.h(或不应该)。

stdlib.h中的内容只是(我的意思是calloc)

extern void *calloc(size_t nmemb, size_t size);

calloclibc.这是您的程序在调用它时将找到它的位置。

严格来说,你不需要任何其他东西(我的意思是,从执行的角度来看)就能调用函数。

所有.c代码都经过编译以完成其任务。libc早就编译好了。在链接时(对于静态库,以及指向它们的动态库),然后在运行时,加载所有编译代码中的所有函数。因此,您的代码和callocmain将在那时找到彼此。

问题不在那里。

问题在于,为了编译你的main,编译器需要知道应该如何调用calloc

它需要知道它以检查语法错误。否则,您可以将 3 个参数或仅传递一个参数传递给calloc,并且编译器将无法知道这是不正确的。 您可以传递错误类型的参数,同样。

它还需要知道它应该作为参数推送多少字节,甚至应该如何传递参数。

例如,请参阅这两个代码

一.c

#include <stdio.h>
void printInt(int a, int b){
printf("ints %d %dn", a, b);
}
void printFloat(float a, float b){
printf("floats %f %fn", a, b);
}
void printDouble(double a, double b){
printf("doubles %f %fn", 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

看到它仅适用于使用 int 调用printInt,以及使用双精度调用printDouble按预期工作。它不能将1.0转换为 int 来调用printInt,或者相反地将1转换为调用printDouble的双精度。对于printFloat,它在所有情况下都失败,因为编译器错误地假设了要推送的参数的大小。

但除此之外,调用了这 3 个函数。缺少的不是函数的代码。这是编译器在调用它们时正确调用它们的能力。

只需添加,在two.c声明

extern void printInt(int, int);
extern void printFloat(float, float);
extern void printDouble(double, double);

(或者创建一个包含这些的one.h,并在two.c#include "one.h",它会导致相同的结果)

现在输出符合预期

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提供编译器需要知道如何调用这些函数的无代码声明。

  1. 该函数不在stdlib.h,它位于同一个共享库对象中,libc.so因此链接器查找符号没有问题。如果尝试调用 made 函数,也不会收到编译器错误,而是链接器会抱怨无法解析符号。

  2. 从技术上讲,你可以,但你绝对不应该。如果你不这样做,你就不会被警告论证错误。链接器不会(也不能)检查是否提供了正确数量和类型的参数。

例:

// a.c
int asdf(int b){
return b+1;
}
// main.c
int main(void) {
asdf();
return 0;
}

如果现在编译:gcc main.c a.c您只会收到警告,而如果您包含标头,则会收到错误。这是BAD,因为它可能导致未定义的行为,从而导致意外崩溃、内存损坏、安全问题等。

您应该始终给您的编译器一个公平的机会来帮助您。

编辑:澄清,忽略是不好的

  1. 为什么 GCC 没有给出错误?因为据我了解,calloc位于stdlib.h,但我没有把它包含在我的程序中。为什么我的程序仍然知道什么是calloc?

为了与旧代码兼容,这不是错误,因此无法中止编译,因此会收到警告。 在旧的旧版 C 代码中,如果函数返回int结果,则可以使用不带声明的函数(如果未为函数指定原型,则假定为该函数)。 旧的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 最新版本中编译没有任何问题。 is 显示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: 
  1. 我们应该在警告时闭上眼睛吗?因为我的程序即使不包含 stdlib.h 也能很好地运行。

我认为你永远不应该在任何警告时闭上眼睛。 如果您只是#include <stdlib.h>,编译器将正确编译源代码而不会发出警告。 关于警告的重要一点是,在继续之前,您应该阅读它们并理解它们(以及忽略它们的后果)。 衰减只是一个建议消息,它不会中断编译,因为这可能是编译器运行的预期目的(只是编译旧的遗留代码,这将产生一个有效的程序,但很久以前编写)

在上面的例子中,你最好做正确的#include <stdio.h>,因为如果你不这样做,你的代码将是错误的。 它将假设calloc()函数返回一个int(在 64 位架构中大小不同),并且该值可能是截断的指针(其中某些部分已被转换为int截断的指针)并且不起作用(但这最近发生, 使用 64 位架构,在 32 位架构上很好,不正确但有效)所以,你可以服从或不服从,但你靠自己。

最新更新