繁体   English   中英

警告:function 'calloc' 的隐式声明

[英]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 )。 我的问题是:

  1. 为什么GCC不报错? 因为据我所知, calloc位于stdlib.h ,但我没有将它包含在我的程序中。 为什么我的程序仍然知道什么是calloc

  2. 我们应该对警告闭上眼睛吗? 因为即使不包含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;
}
  1. function 不在stdlib.h中,它位于同一个共享库 object, libc.so中,因此您的 linker 可以毫无问题地找到符号。 如果您尝试调用虚构的 function,您也不会收到编译器错误,而您的 linker 会抱怨无法解析符号。

  2. 你在技术上可以,但你绝对不应该。 如果你不这样做,你就不会收到有关参数错误的警告。 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);

calloclibc中。 那就是你的程序在调用它时会找到它的地方。

严格来说,您不需要任何其他东西(我的意思是,从执行的角度来看)就能调用 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用于提供编译器需要知道如何调用这些函数的无代码声明。

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

为了与遗留代码兼容,这不是错误,因此无法中止编译,因此您会收到警告。 在旧的遗留 C 代码中,如果它返回一个int结果(如果您没有为函数指定原型,则假定),您可以使用没有声明的 function。 旧的 K&R 代码在 C98 中仍然有效(我不能保证以后的标准版本,但至少我已经测试了 K&R 代码并且它编译时最多有一些可以忽略的警告)所以它必须被编译成工作代码(但仍然可能是无效代码,因为生成的调用 calloc 的代码会考虑返回一个intcalloc确实返回一个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: 
  1. 我们应该对警告闭上眼睛吗? 因为即使不包含 stdlib.h,我的程序也能正常运行。

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM