警告:函式'calloc'的隐含宣告

pgpifvop  于 2022-12-02  发布在  其他
关注(0)|答案(4)|浏览(192)
#include<stdio.h>

int *arr;

int main()
{
   arr = calloc(1, sizeof(int));

   free(arr);
   return 0;
}

据我所知,出现这个警告是因为我没有在header中声明函数(在本例中,我应该包含stdlib.h)。我的问题是:
1.为什么愚者没有给予错呢?因为据我所知,calloc位于stdlib.h,但我没有把它包含在我的程序中,为什么我的程序仍然知道什么是calloc
1.我们应该闭上眼睛吗?因为我的程序即使不包含stdlib.h也能很好地工作。

m528fe3b

m528fe3b1#

据我所知,出现这个警告是因为我没有在header中声明函数(在本例中,我应该包含<stdlib.h>)。
正确。编译器抱怨您正在调用一个它没有看到声明的函数。包含<stdlib.h>确实为函数calloc提供了一个正确的声明。请注意,您也可以通过添加以下行自己提供一个声明:void *calloc(size_t, size_t);。但是,建议使用标准的包含文件来获取库函数的确切声明。
为什么愚者没有给予错误?
因为编译器对编译那些在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 %p\n", (void *)arr);

    free(arr);
    return 0;
}
2jcobegt

2jcobegt2#

calloc不在stdlib.h中。stdlib.h中没有任何内容。任何.h中没有任何内容(或应该没有任何内容)。
stdlib.h中的内容只是(我的意思是大约calloc

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

calloclibc中。当程序调用它时,它将在那里找到它。
严格地说,您不需要任何其他东西(我的意思是,从执行的Angular 来看)就可以调用函数。
所有的.c代码都被编译来完成它们的任务。libc很早以前就被编译过了。在链接时(对于静态库,以及指向它们的动态库),然后在运行时,所有编译过的代码中的所有函数都被加载。所以你的代码中的maincalloc会在那时找到对方。
问题不在那里。
问题在于,为了编译main,编译器需要知道应该如何调用calloc
它需要知道它来检查语法错误。否则你可能会传递3个参数,或者只有一个,到calloc,编译器将无法知道这是不正确的。你可能会传递错误类型的参数,等等。
它还需要知道它应该作为参数推送多少字节,甚至应该如何传递参数。
例如,请参阅这两个代码
one.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

#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和使用double调用的printDouble有效。它无法将1.0转换为int来调用printInt,或者相反,无法将1转换为double来调用printDouble。对于printFloat,它在所有情况下都失败。因为编译器错误地假定了要压入的参数的大小。
但除此之外,这3个函数都被调用了。缺少的不是函数的代码,而是编译器在调用它们时正确调用它们的能力。
只需在two.c中添加声明

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

(Or创建一个包含这些元素的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提供了编译器需要知道如何调用这些函数的无代码声明。

nqwrtyyt

nqwrtyyt3#

1.函数并不位于stdlib.h,它位于同一个共享库对象libc.so中,所以链接器找到符号没有问题。如果你试图调用一个构造函数,你也不会得到编译器错误,相反,你的链接器会抱怨无法解析符号。
1.技术上可以,但绝对不应该。如果你不这样做,就不会收到参数错误的警告。如果你提供了正确的参数数量和类型,链接器不会(也不能)检查。
示例:

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

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

如果现在编译:gcc main.c a.c您只会收到一个警告,而如果您包含了头文件,则会收到一个错误。这是BAD,因为它可能导致未定义的行为,从而导致意外崩溃、内存损坏、安全问题等。
你应该给予你的编译器一个公平的机会来帮助你。

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

xqk2d5yq

xqk2d5yq4#

1.为什么愚者不给予错呢?因为据我所知,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的pdp 11(发布前刚刚测试过)和gcc最新版本中编译没有任何问题。

$ 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位架构上很好,不正确,但工作正常)所以,你可以服从或不服从,但你只能靠自己了。

相关问题