顯示,如果你讓編譯器推斷這並不總是聲明函數返回一個int
只會編譯代碼。這在C89/C90中是有效的,但是過時了; C99和C11要求在使用它們之前聲明函數。 GCC版本5.1.0之前的GCC默認採用C90模式;您必須打開「拒絕此代碼」警告。 GCC 5.1.0及更高版本默認採用C11。即使沒有任何編譯選項,您至少也會從代碼中獲取警告。
代碼會正常鏈接,因爲函數名稱是strerror()
,無論它是否聲明,鏈接器都可以在標準C庫中找到該函數。通常,標準C庫中的所有函數都可以自動用於鏈接 - 實際上,通常還有許多不那麼標準的函數可用。 C沒有像C++那樣的類型安全鏈接(但是C++也堅持要在使用它之前聲明每個函數,所以代碼不會像C++那樣在沒有頭的情況下編譯)。
由於歷史原因,數學庫是分開的,您需要指定-lm
才能鏈接它。這在很大程度上是因爲硬件浮點不是通用的,所以有些機器需要一個使用硬件的庫,而其他機器需要軟件仿真浮點算法。某些平臺(例如Linux)如果使用<math.h>
(可能是<tgmath.h>
)中聲明的函數,仍然需要單獨的-lm
選項;其他平臺(例如Mac OS X)不會 - 有一個-lm
來滿足構建系統的鏈接,但數學函數在主C庫中。
如果代碼是一個相當標準的32位平臺ILP32(int
,long
,指針所有32位),在編譯然後許多體系,假定strerror()
返回一個int
假定它返回相同量的數據好像它返回char *
(這是strerror()
實際返回的值)。因此,當代碼將strerror()
的返回值壓入fprintf()
的堆棧時,會推送正確的數據量。請注意,某些架構(特別是摩托羅拉M680x0系列)將返回地址寄存器(A0)中的地址和通用寄存器(D0)中的數字,因此即使在具有32位編譯的機器上也會出現問題:編譯器會嘗試從數據寄存器而不是地址寄存器中獲取返回值,並且這不是由strerror()
設置的 - 導致混亂。
隨着64位體系結構(LP64),假設strerror()
返回一個32位int
意味着,編譯器將僅收集由strerror()
返回的64位地址的32位和推送堆棧上爲fprintf()
到與...合作。當它試圖將截斷地址視爲有效時,事情就會出錯,經常導致崩潰。
當添加缺少的<string.h>
頭,編譯器知道該strerror()
函數返回一個char *
和所有的幸福和喜悅再次,即使該文件的程序被告知要尋找不存在。
如果你是明智的,你將確保你的編譯器總是在繁瑣的模式下編譯,拒絕任何可能錯誤的東西。當我用我的默認的編譯您的代碼,我得到:
$ gcc -std=c11 -O3 -g -Wall -Wextra -Werror -Wmissing-prototypes \
> -Wstrict-prototypes -Wold-style-definition bogus.c -o bogus
bogus.c: In function ‘main’:
bogus.c:10:33: error: implicit declaration of function ‘strerror’ [-Werror=implicit-function-declaration]
fprintf(stderr, "%s\n", strerror(errno));
^
bogus.c:10:25: error: format ‘%s’ expects argument of type ‘char *’, but argument 3 has type ‘int’ [-Werror=format=]
fprintf(stderr, "%s\n", strerror(errno));
^
bogus.c:10:25: error: format ‘%s’ expects argument of type ‘char *’, but argument 3 has type ‘int’ [-Werror=format=]
bogus.c:4:14: error: unused parameter ‘argc’ [-Werror=unused-parameter]
int main(int argc, char *argv[])
^
cc1: all warnings being treated as errors
$
的「未使用的參數」的錯誤提醒你,你應該檢查到有傳遞給fopen()
您嘗試打開文件之前的說法。
固定碼:
#include <string.h>
#include <errno.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
FILE *fp;
if (argc != 2)
{
fprintf(stderr, "Usage: %s file\n", argv[0]);
return 1;
}
fp = fopen(argv[1], "r");
if (fp == NULL)
{
fprintf(stderr, "%s: file %s could not be opened for reading: %s\n",
argv[0], argv[1], strerror(errno));
return errno;
}
printf("file %s exists\n", argv[1]);
fclose(fp);
return 0;
}
體形:
$ gcc -std=c11 -O3 -g -Wall -Wextra -Werror -Wmissing-prototypes \
> -Wstrict-prototypes -Wold-style-definition bogus.c -o bogus
$
運行:
$ ./bogus bogus
file bogus exists
$ ./bogus bogus2
./bogus: file bogus2 could not be opened for reading: No such file or directory
$ ./bogus
Usage: ./bogus file
$
注意,錯誤消息包括節目名稱和標準錯誤報告。當文件已知時,錯誤消息包含文件名;這是很容易調試一個錯誤,如果該程序是在shell腳本比如果消息只是:
No such file or directory
沒有指出哪些程序或文件時遇到的問題。
當我刪除從固定代碼#include <string.h>
線所示,那麼我可以編譯並像這樣運行:
$ gcc -o bogus90 bogus.c
bogus.c: In function ‘main’:
bogus.c:18:35: warning: implicit declaration of function ‘strerror’ [-Wimplicit-function-declaration]
argv[0], argv[1], strerror(errno));
^
$ gcc -std=c90 -o bogus90 bogus.c
$ ./bogus90 bogus11
Segmentation fault: 11
$
將其用在Mac OS X 10.10.5 GCC 5.1.0測試 - 當然,這是一個64位平臺。
我知道,但如何鏈接器鏈接這個字符串庫沒有我明確提供'#包括'在開始 –
codeomnitrix
我發現這*** [對話](http://stackoverflow.com/a/12879085/ 645128)***可能有助於回答... – ryyker
鏈接器不關心'#include'指令。鏈接器鏈接它被告知鏈接的內容。 –