简体   繁体   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;
}

As far as I understand, this warning occurs because I did not declare the function in header (In this case I should've included stdlib.h ).据我了解,出现此警告是因为我没有在 header 中声明 function (在这种情况下,我应该包含stdlib.h )。 My questions are:我的问题是:

  1. Why doesn't the GCC give an error?为什么GCC不报错? Because as far as I understand, calloc is located at stdlib.h , but I didn't include it in my program.因为据我所知, calloc位于stdlib.h ,但我没有将它包含在我的程序中。 Why does my program still know what is calloc ?为什么我的程序仍然知道什么是calloc

  2. Should we close our eyes at the warning?我们应该对警告闭上眼睛吗? Because my program works well even without include of stdlib.h .因为即使不包含stdlib.h ,我的程序也能正常运行。

As far as I understand, this warning occurs because I did not declare the function in header (In this case I should've included <stdlib.h> ).据我所知,出现此警告是因为我没有在 header 中声明 function(在这种情况下我应该包含<stdlib.h> )。

You are correct.你是对的。 The compiler complains that you are calling a function for which it has not seen a declaration.编译器抱怨您正在调用 function,但它没有看到它的声明。 Including <stdlib.h> does provide a proper declaration for the function calloc .包括<stdlib.h>确实为 function calloc提供了正确的声明。 Note that you could also have provided a declaration yourself by adding the line: void *calloc(size_t, size_t);请注意,您也可以通过添加以下行自己提供声明: void *calloc(size_t, size_t); . . Yet it is recommended to use the standard include files to get the exact declarations for library functions.然而,建议使用标准包含文件来获取库函数的准确声明。

Why doesn't the GCC give an error?为什么GCC不报错?

Because the compiler is lenient in order to compile older programs that were written before the C Standard was even published.因为编译器很宽松,可以编译在 C 标准发布之前编写的旧程序。 It used to not be an error to call functions without a declaration or a definition in scope. The compiler would just infer the prototype from the types of the arguments provided.在 scope 中调用没有声明或定义的函数过去不会出错。编译器只会根据提供的 arguments 的类型推断原型。 In your case the prototype is inferred as int calloc(int, size_t) , which is obviously incorrect and will cause undefined behavior.在您的情况下,原型被推断为int calloc(int, size_t) ,这显然是不正确的并且会导致未定义的行为。 To avoid such problems, the compiler issues a warning about calloc being undeclared.为避免此类问题,编译器会发出有关未声明calloc的警告。

Why does my program still know what is calloc ?为什么我的程序仍然知道什么是calloc

It does not, the prototype is inferred from the call statement and this guess falls short.事实并非如此,原型是从调用语句中推断出来的,这个猜测不成立。 As a matter of fact, the compiler should also complain about the int return value being implicitly converted to an int * when stored to arr .事实上,编译器还应该抱怨int返回值在存储到arr时被隐式转换为int * If int * and int have a different representation on the target system (as is the case on 64-bit systems), the value of arr will be invalid and free(arr) will definitely have undefined behavior too.如果int *int在目标系统上有不同的表示(如在 64 位系统上的情况),则arr的值将无效并且free(arr)肯定也会有未定义的行为。 The compiler produces an executable because the function calloc is found in the C library, which is linked to all C programs implicitly.编译器生成可执行文件,因为 function calloc位于 C 库中,它隐式链接到所有 C 程序。 The argument and return types are not checked at link time for C programs. C 程序在链接时不检查参数和返回类型。

Should we close our eyes at the warning?我们应该对警告闭上眼睛吗?

Definitely not.当然不。 You should compile your program with all warnings enabled using gcc -Wall -Wextra -Werror , and gcc will consider all warnings like fatal errors and refuse to produce an executable until they are corrected.您应该使用gcc -Wall -Wextra -Werror启用所有警告来编译您的程序,并且 gcc 将考虑所有警告,如致命错误,并拒绝生成可执行文件,直到它们被更正。 This will save you many hours of debugging time.这将为您节省许多小时的调试时间。 It is a pity this behavior is not the default one.遗憾的是,这种行为不是默认行为。

my program works well even without include of <stdlib.h> .即使不包含<stdlib.h>我的程序也能正常运行。

Well it appears to work on your system, but it fails on mine (assuming I remove my default compiler options that prevent gcc from making an executable).好吧,它似乎可以在您的系统上运行,但在我的系统上却失败了(假设我删除了阻止gcc生成可执行文件的默认编译器选项)。 The program has undefined behavior.该程序具有未定义的行为。 Do not rely on chance.不要依赖机会。

Here is a proper version:这是一个正确的版本:

#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. The function is not located at stdlib.h , it is in the same shared library object, libc.so so your linker has no problem finding the symbol. function 不在stdlib.h中,它位于同一个共享库 object, libc.so中,因此您的 linker 可以毫无问题地找到符号。 If you tried to call a made up function, you also wouldn't get a compiler error, rather your linker would complain about being unable to resolve a symbol.如果您尝试调用虚构的 function,您也不会收到编译器错误,而您的 linker 会抱怨无法解析符号。

  2. You technically can, but you absolutely shouldn't.你在技术上可以,但你绝对不应该。 If you wouldn't you wouldn't be warned about argument errors.如果你不这样做,你就不会收到有关参数错误的警告。 The Linker does not (and can not) check, if you supplied the right amount and types of arguments. Linker 不会(也不能)检查您是否提供了正确数量和类型的 arguments。

Example:例子:

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

// main.c
int main(void) {
   asdf();
   return 0;
}

If you compile now: gcc main.c a.c You will only get a warning, while if you had included a header you would get an error.如果你现在编译: gcc main.c a.c你只会得到一个警告,而如果你包含了一个 header 你会得到一个错误。 This is BAD , since it can lead to undefined behavior that can lead to unexpected crashes, memory corruption, security issues, etc.这是BAD ,因为它可能导致未定义的行为,从而导致意外崩溃、memory 损坏、安全问题等。

You should always give your compiler a fair opportunity to help you.你应该总是给你的编译器一个公平的机会来帮助你。

EDIT: Clarify, that ignoring is bad编辑:澄清一下,忽略是不好的

calloc is not in stdlib.h . calloc不在stdlib.h中。 Nothing is in stdlib.h . stdlib.h中没有任何内容。 Nothing is in any .h (or nothing should). .h中什么都没有(或者什么都不应该)。

What is in stdlib.h is just (about calloc I mean) stdlib.h中的内容只是(我的意思是关于calloc

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

calloc is in the libc . calloclibc中。 That is where your program will find it when it will call it.那就是你的程序在调用它时会找到它的地方。

Strictly speaking, you don't need anything else (I mean, from an execution point of view) to be able to call a function.严格来说,您不需要任何其他东西(我的意思是,从执行的角度来看)就能调用 function。

All .c codes are compiled to do their task.编译所有.c代码来完成它们的任务。 libc has been compiled long ago. libc早就编译好了。 And at linking time (for static libraries, and for dynamic to point to them) and then at run time, all functions from all compiled code are loaded.在链接时(对于 static 库,动态指向它们)然后在运行时,加载所有编译代码中的所有函数。 So your main from your code and calloc will find each other at that time.所以你的main from your code 和calloc会在那个时候找到对方。

The problem is not there.问题不在那里。

The problem is that in order to compile your main, the compiler need to know how calloc is supposed to be called.问题是为了编译你的 main,编译器需要知道应该如何调用calloc

It needs to know it to check for syntax error.它需要知道它来检查语法错误。 Otherwise you could pass 3 arguments, or only one, to calloc , and the compiler would have no way to know that this is not correct.否则,您可以将 3 arguments 或仅一个传递给calloc ,编译器将无法知道这是不正确的。 You could pass arguments of the wrong type, and likewise.您可以传递错误类型的 arguments,同样。

It needs to know also to know how many bytes it is supposed to push as an argument, and even how it is supposed to pass arguments.它还需要知道应该将多少字节作为参数推送,甚至应该如何传递 arguments。

See for example these two codes参见例如这两个代码

one.c一.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);
}

two.c二.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);
}

Compile them with (depending on your compiler)编译它们(取决于你的编译器)

gcc -std=gnu99 -o main one.c two.c # There is an implicit -lc here including libc, that contains printf

On my computer it prints在我的电脑上打印

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

See that it works as intended only for printInt called with ints, and printDouble called with doubles.看到它仅适用于使用整数调用的printInt和使用双打调用的printDouble It fails to cast 1.0 as an int to call printInt , or on the contrary to cast 1 as a double to call printDouble .它无法将1.0转换为 int 来调用printInt ,或者相反,无法将1转换为 double 来调用printDouble And For printFloat it fails in all cases, because the compiler assumed wrongly the size of the arguments to be pushed.对于printFloat ,它在所有情况下都会失败,因为编译器错误地假设了要推送的 arguments 的大小。

But other than that, those 3 functions are called.但除此之外,这 3 个函数被调用。 It is not the the code of the function that is missing.丢失的不是 function 的代码。 It is the ability of the compiler, when calling them, to call them correctly.这是编译器在调用它们时正确调用它们的能力。

Just add, in two.c the declarations只需添加two.c声明

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

(Or create a one.h containing those, and #include "one.h" in two.c , it leads to the same result) (或者创建一个包含这些的one.h ,并在two.c#include "one.h" ,它会导致相同的结果)

And now the output is as expected现在 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

And I haven't even started with types declarations.我什至还没有开始类型声明。

#include are not meant to provide libraries, and the functions that are in them. #include并不意味着提供库和其中的函数。 That you do while linking, adding .o and -lsomelib to the liking command line (or using other way, depending on your compiler).您在链接时所做的,将.o-lsomelib添加到喜欢的命令行(或使用其他方式,具体取决于您的编译器)。

#include are there to provide codeless declarations that the compiler needs to know how to call those functions. #include用于提供编译器需要知道如何调用这些函数的无代码声明。

  1. Why doesn't the GCC give an error?为什么GCC不报错? Because as far as I understand, calloc is located at stdlib.h, but I didn't include it in my program.因为据我了解,calloc 位于 stdlib.h,但我没有将它包含在我的程序中。 Why does my program still know what is calloc?为什么我的程序仍然知道什么是 calloc?

For compatibility with legacy code, this is not an error, so compilation cannot be aborted, and so, you get a warning instead.为了与遗留代码兼容,这不是错误,因此无法中止编译,因此您会收到警告。 In old legacy C code, you can use a function without a declaration if it returns an int result (which is assumed if you don't specify a prototype for the function).在旧的遗留 C 代码中,如果它返回一个int结果(如果您没有为函数指定原型,则假定),您可以使用没有声明的 function。 Old K&R code is still valid in C98 (I cannot ensure about later standard editions, but at least I have tested K&R code and it compiles with at most some warnings that can be ignored) and so it must be compiled into working code (but still probably invalid code, as the code generated to call calloc will consider an int is being returned---while calloc indeed returns a void * pointer type, which in 64bit architectures is 64bit)旧的 K&R 代码在 C98 中仍然有效(我不能保证以后的标准版本,但至少我已经测试了 K&R 代码并且它编译时最多有一些可以忽略的警告)所以它必须被编译成工作代码(但仍然可能是无效代码,因为生成的调用 calloc 的代码会考虑返回一个intcalloc确实返回一个void *指针类型,在 64 位架构中是 64 位)

Just try this code, and see:)试试这段代码,然后看看:)

main(argc, argv)
char **argv;
{
    int i;
    char *sep = "";
    for (i = 0; i < argc; i++) {
        printf("%s[%s]", sep, *argv++);
        sep = ", ";
    }
    puts("");
}

The code above compiles without any problem in a pdp11 runing unix v7 (just tested before posting) and in gcc last version.上面的代码在运行 unix v7(发布前刚刚测试过)的 pdp11 和最新版本 gcc 中编译没有任何问题。 Is shows you the argv command parameter list on stdout .是向您显示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. Should we close our eyes at the warning?我们应该对警告闭上眼睛吗? Because my program works well even without include of stdlib.h.因为即使不包含 stdlib.h,我的程序也能正常运行。

I think you should never close your eyes at any warning.我认为你永远不应该在任何警告时闭上眼睛。 If you just #include <stdlib.h> , the compiler will compile correctly the source code without a warning.如果您只是#include <stdlib.h> ,编译器将在没有警告的情况下正确编译源代码。 The important thing about warnings is that YOU SHOULD READ THEM, AND UNDERSTAND THEM (and the consequences of ignoring them) BEFORE CONTINUING.关于警告的重要一点是您应该在继续之前阅读并理解它们(以及忽略它们的后果)。 A waning is just an advisory message, it doesn't interrupt the compilation because this can be the intended purpose of the compiler run (simply to compile old, legacy code, that will produce a valid program, but written a long time ago)减弱只是一个建议消息,它不会中断编译,因为这可能是编译器运行的预期目的(只是编译旧的遗留代码,这将产生一个有效的程序,但是很久以前写的)

In the case above, you had better to do the proper #include <stdio.h> , because if you don't, your code will be erroneous.在上面的例子中,你最好做正确的#include <stdio.h> ,因为如果你不这样做,你的代码就会出错。 It will assume the calloc() function returns an int (which is not the same size in 64bit architectures) and the value will probably will be a truncated pointer (a pointer in which some part of it has been truncated by the conversion to an int ) and will not work (but this happens recently, with 64bit architectures, it was fine on 32bit ones, incorrect but working) So, you can obey or disobey, but you are on your own.它将假定calloc() function 返回一个int (在 64 位体系结构中大小不同)并且该值可能是一个截断的指针(一个指针,其中的某些部分已通过转换为int被截断) 并且不会工作(但是最近发生了这种情况,使用 64 位架构,在 32 位架构上很好,不正确但有效)所以,你可以服从或不服从,但你是你自己的。

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

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