簡體   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