[英]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
)。 我的問題是:
為什么GCC不報錯? 因為據我所知, calloc
位於stdlib.h
,但我沒有將它包含在我的程序中。 為什么我的程序仍然知道什么是calloc
?
我們應該對警告閉上眼睛嗎? 因為即使不包含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;
}
function 不在stdlib.h
中,它位於同一個共享庫 object, libc.so
中,因此您的 linker 可以毫無問題地找到符號。 如果您嘗試調用虛構的 function,您也不會收到編譯器錯誤,而您的 linker 會抱怨無法解析符號。
你在技術上可以,但你絕對不應該。 如果你不這樣做,你就不會收到有關參數錯誤的警告。 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);
calloc
在libc
中。 那就是你的程序在調用它時會找到它的地方。
嚴格來說,您不需要任何其他東西(我的意思是,從執行的角度來看)就能調用 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
用於提供編譯器需要知道如何調用這些函數的無代碼聲明。
- 為什么GCC不報錯? 因為據我了解,calloc 位於 stdlib.h,但我沒有將它包含在我的程序中。 為什么我的程序仍然知道什么是 calloc?
為了與遺留代碼兼容,這不是錯誤,因此無法中止編譯,因此您會收到警告。 在舊的遺留 C 代碼中,如果它返回一個int
結果(如果您沒有為函數指定原型,則假定),您可以使用沒有聲明的 function。 舊的 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 中編譯沒有任何問題。 是向您顯示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:
- 我們應該對警告閉上眼睛嗎? 因為即使不包含 stdlib.h,我的程序也能正常運行。
我認為你永遠不應該在任何警告時閉上眼睛。 如果您只是#include <stdlib.h>
,編譯器將在沒有警告的情況下正確編譯源代碼。 關於警告的重要一點是您應該在繼續之前閱讀並理解它們(以及忽略它們的后果)。 減弱只是一個建議消息,它不會中斷編譯,因為這可能是編譯器運行的預期目的(只是編譯舊的遺留代碼,這將產生一個有效的程序,但是很久以前寫的)
在上面的例子中,你最好做正確的#include <stdio.h>
,因為如果你不這樣做,你的代碼就會出錯。 它將假定calloc()
function 返回一個int
(在 64 位體系結構中大小不同)並且該值可能是一個截斷的指針(一個指針,其中的某些部分已通過轉換為int
被截斷) 並且不會工作(但是最近發生了這種情況,使用 64 位架構,在 32 位架構上很好,不正確但有效)所以,你可以服從或不服從,但你是你自己的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.