2015-09-25 103 views
1

我剛剛遇到這個代碼,博客表示這在32位體系結構上工作正常。我沒有測試它;然而,在這種情況下,我對圖書館的聯繫表示懷疑。編譯器如何將字符串庫鏈接到main,因爲它不知道要鏈接哪個庫?在這種情況下庫函數是如何鏈接的?

所以基本上如果我包括<string.h>那麼它應該可以正常工作;但是,如果我不包含<string.h>,那麼按照博客,它將運行在32位體系結構中,並且無法在64位體系結構上運行。

#include <errno.h> 
#include <stdio.h> 

int main(int argc, char *argv[]) 
{ 
    FILE *fp; 

    fp = fopen(argv[1], "r"); 
    if (fp == NULL) { 
     fprintf(stderr, "%s\n", strerror(errno)); 
     return errno; 
    } 

    printf("file exist\n"); 

    fclose(fp); 

    return 0; 
} 
+0

我知道,但如何鏈接器鏈接這個字符串庫沒有我明確提供'#包括'在開始 – codeomnitrix

+1

我發現這*** [對話](http://stackoverflow.com/a/12879085/ 645128)***可能有助於回答... – ryyker

+0

鏈接器不關心'#include'指令。鏈接器鏈接它被告知鏈接的內容。 –

回答

5

顯示,如果你讓編譯器推斷這並不總是聲明函數返回一個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(intlong,指針所有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位平臺。

1

我不認爲這個代碼的功能會受到其32位或64位架構的影響:指針是32位還是64位,如果long int是32位或64位。包含頭文件(在本例中爲string.h)不應該影響與庫的鏈接。頭文件包含對編譯器而不是鏈接器。編譯器可能會警告隱式聲明的函數,但只要鏈接器可以在其中一個正在搜索的庫中找到該函數,它就會成功鏈接該二進制文件,並且它應該運行得很好。

我剛剛在64位CentOS盒子上使用叮噹聲3.6.2創建並運行了該代碼。我沒有得到這個編譯器警告:

junk.c:10:33: warning: implicitly declaring library function 'strerror' with type 'char *(int)' 
     fprintf(stderr, "%s\n", strerror(errno)); 
           ^
junk.c:10:33: note: include the header <string.h> or explicitly provide a declaration for 'strerror' 
1 warning generated. 

該節目進行了一個不存在的文件名和錯誤消息,「沒有這樣的文件或目錄」,是有意義的。然而,這是因爲strerror()函數是一個衆所周知的標準庫函數,並且它的聲明被編譯器正確地猜到了。如果它是用戶定義的函數,那麼編譯器在猜測時可能不那麼「幸運」,然後架構可能很重要,正如其他答案所建議的那樣。

因此,吸取的教訓:確保函數聲明可用於編譯器並注意警告!

+0

什麼是編譯器版本?你用來編譯的命令行是什麼?您是否使用命令行上的文件運行程序?該文件是否存在?你有沒有得到任何編譯器警告? –

+3

它受到影響,因爲'strerror'返回'char *'。不包括頭文件,編譯器不知道這個,並假定它返回一個'int'。這發生在32位平臺上,因爲它們的大小相同。但是在指針是整數大小的兩倍的64位平臺上它會發生可怕的錯誤。 –

+0

請注意,編譯器(clang)使用其標準C的知識來推斷函數的正確類型,因此儘管缺少聲明,程序仍能正常工作。相比之下,當我使用GCC時,它沒有使用它的知識來進行類型推斷,所以我崩潰了。有趣。確保代碼編譯沒有警告仍然好得多 - 當然在默認模式下,最好甚至當編譯器設置得相當模糊時。 ('clang -Weverything'雖然很難滿足,但是!) –