首先消除gcc和g++誤區(qū)吧。
gcc和g++都是GNU(組織)的一個編譯器。
誤區(qū)一:gcc只能編譯c代碼,g++只能編譯c++代碼
兩者都可以,但是請注意:
1.后綴為.c的,gcc把它當(dāng)作是C程序,而g++當(dāng)作是c++程序;后綴為.cpp的,兩者都會認(rèn)為是c++程序,注意,雖然c++是c的超集,但是兩者對語法的要求是有區(qū)別的,例如:
#include <stdio.h>
int main(int argc, char* argv[]) {
}
int printString(char* string) {
sprintf(string, "This is a test."n");
}
如果按照C的語法規(guī)則,OK,沒問題,但是,一旦把后綴改為cpp,立刻報三個錯:“printString未定義”;
“cannot convert `char**' to `char*”;
”return-statement with no value“;
分別對應(yīng)前面紅色標(biāo)注的部分。可見C++的語法規(guī)則更加嚴(yán)謹(jǐn)一些。
2.編譯階段,g++會調(diào)用gcc,對于c++代碼,兩者是等價的,但是因為gcc命令不能自動和C++程序使用的庫聯(lián)接,所以通常用g++來完成鏈接,為了統(tǒng)一起見,干脆編譯/鏈接統(tǒng)統(tǒng)用g++了,這就給人一種錯覺,好像cpp程序只能用g++似的。
實際上,這個宏只是標(biāo)志著編譯器將會把代碼按C還是C++語法來解釋,如上所述,如果后綴為.c,并且采用gcc編譯器,則該宏就是未定義的,否則,就是已定義。
誤區(qū)三:編譯只能用gcc,鏈接只能用g++
嚴(yán)格來說,這句話不算錯誤,但是它混淆了概念,應(yīng)該這樣說:編譯可以用gcc/g++,而鏈接可以用g++或者gcc-lstdc++。因為gcc命令不能自動和C++程序使用的庫聯(lián)接,所以通常使用g++來完成聯(lián)接。但在編譯階段,g++會自動調(diào)用gcc,二者等價。
誤區(qū)四:extern "C"與gcc/g++有關(guān)系
實際上并無關(guān)系,無論是gcc還是g++,用extern"c"時,都是以C的命名方式來為symbol命名,否則,都以c++方式命名。試驗如下:
me.h:
extern "C" void CppPrintf(void);
me.cpp:
#include <iostream>
#include "me.h"
using namespace std;
void CppPrintf(void)
{
}
test.cpp:
#include <stdlib.h>
#include <stdio.h>
#include "me.h"
int main(void)
{
}
1. 先給me.h加上extern "C",看用gcc和g++命名有什么不同
[root@root G++]# g++ -S me.cpp
[root@root G++]# less me.s
.globl _Z9CppPrintfv
[root@root GCC]# gcc -S me.cpp
[root@root GCC]# less me.s
.globl _Z9CppPrintfv
完全相同!
2. 去掉me.h中extern "C",看用gcc和g++命名有什么不同
[root@root GCC]# gcc -S me.cpp
[root@root GCC]# less me.s
.globl _Z9CppPrintfv
[root@root G++]# g++ -S me.cpp
[root@root G++]# less me.s
.globl _Z9CppPrintfv
完全相同!
【結(jié)論】完全相同,可見extern "C"與采用gcc/g++并無關(guān)系,以上的試驗還間接的印證了前面的說法:在編譯階段,g++是調(diào)用gcc的。
下面就具體來查看一下gcc是如何完成四個步驟的。
hello.c源代碼
#include<stdio.h>
int main()
{
}
(1)預(yù)處理階段
在該階段,編譯器將上述代碼中的stdio.h編譯進(jìn)來,并且用戶可以使用gcc的選項”-E”進(jìn)行查看,該選項的作用是讓gcc在預(yù)處理結(jié)束后停止編譯過程。
《深入理解計算機系統(tǒng)》中是這么說的:
注意:
其中,目標(biāo)文件可缺省,Gcc默認(rèn)生成可執(zhí)行的文件名為:編譯文件.out
[gan@localhost gcc]# gcc –E hello.c –o hello.i
選項”-o”是指目標(biāo)文件,”.i”文件為已經(jīng)過預(yù)處理的C原始程序。以下列出了hello.i文件的部分內(nèi)容:
typedef int (*__gconv_trans_fct) (struct__gconv_step *,
…
# 2 "hello.c" 2
int main()
{
printf("Hello World!\n");
return 0;
}
由此可見,gcc確實進(jìn)行了預(yù)處理,它把”stdio.h”的內(nèi)容插入到hello.i文件中。
(2)編譯階段
接下來進(jìn)行的是編譯階段,在這個階段中,Gcc首先要檢查代碼的規(guī)范性、是否有語法錯誤等,以確定代碼的實際要做的工作,在檢查無誤后,Gcc把代碼翻譯成匯編語言。用戶可以使用”-S”選項來進(jìn)行查看,該選項只進(jìn)行編譯而不進(jìn)行匯編,生成匯編代碼。匯編語言是非常有用的,它為不同高級語言不同編譯器提供了通用的語言。如:C編譯器和Fortran編譯器產(chǎn)生的輸出文件用的都是一樣的匯編語言。
[gan@localhost gcc]# gcc –S hello.i –o hello.s
以下列出了hello.s的內(nèi)容,可見Gcc已經(jīng)將其轉(zhuǎn)化為匯編了,感興趣的讀者可以分析一下這一行簡單的C語言小程序是如何用匯編代碼實現(xiàn)的。
.LC0:
.globl main
main:
(3)匯編階段
匯編階段是把編譯階段生成的”.s”文件轉(zhuǎn)成目標(biāo)文件,讀者在此可使用選項”-c”就可看到匯編代碼已轉(zhuǎn)化為”.o”的二進(jìn)制目標(biāo)代碼了。如下所示:
[gan@localhost gcc]# gcc –c hello.s –o hello.o
(4)鏈接階段
在成功編譯之后,就進(jìn)入了鏈接階段。在這里涉及到一個重要的概念:函數(shù)庫。
在這個源程序中并沒有定義”printf”的函數(shù)實現(xiàn),且在預(yù)編譯中包含進(jìn)的”stdio.h”中也只有該函數(shù)的聲明,而沒有定義函數(shù)的實現(xiàn),那么,是在哪里實現(xiàn)”printf”函數(shù)的呢?最后的答案是:系統(tǒng)把這些函數(shù)實現(xiàn)都被做到名為libc.so.6的庫文件中去了,在沒有特別指定時,gcc會到系統(tǒng)默認(rèn)的搜索路徑”/usr/lib”下進(jìn)行查找,也就是鏈接到libc.so.6庫函數(shù)中去,這樣就能實現(xiàn)函數(shù)”printf”了,而這也就是鏈接的作用。
函數(shù)庫一般分為靜態(tài)庫和動態(tài)庫兩種。靜態(tài)庫是指編譯鏈接時,把庫文件的代碼全部加入到可執(zhí)行文件中,因此生成的文件比較大,但在運行時也就不再需要庫文件了。其后綴名一般為”.a”。動態(tài)庫與之相反,在編譯鏈接時并沒有把庫文件的代碼加入到可執(zhí)行文件中,而是在程序執(zhí)行時由運行時鏈接文件加載庫,這樣可以節(jié)省系統(tǒng)的開銷。動態(tài)庫一般后綴名為”.so”,如前面所述的libc.so.6就是動態(tài)庫。gcc在編譯時默認(rèn)使用動態(tài)庫。
(Linux下動態(tài)庫文件的擴展名為".so"(SharedObject)。按照約定,所有動態(tài)庫文件名的形式是libname.so(可能在名字中加入版本號)。這樣,線程函數(shù)庫被稱作libthread.so。靜態(tài)庫的文件名形式是libname.a。共享archive的文件名形式是libname.sa。共享archive只是一種過渡形式,幫助人們從靜態(tài)庫轉(zhuǎn)變到動態(tài)庫。)
完成了鏈接之后,gcc就可以生成可執(zhí)行文件,如下所示。
[gan@localhost gcc]# gcc hello.o –o hello
運行該可執(zhí)行文件,出現(xiàn)正確的結(jié)果如下。
[root@localhost Gcc]# ./hello
Hello World!
這一節(jié),我們來關(guān)注下gcc的常用參數(shù) ,有機會也好多加練習(xí)啦。
[參數(shù)詳解]
-c
只激活預(yù)處理,編譯,和匯編,也就是他只把程序做成obj文件
例子用法:
gcc -c hello.c
他將生成.o的obj文件
-S
只激活預(yù)處理和編譯,就是指把文件編譯成為匯編代碼。
例子用法
gcc -S hello.c
他將生成.s的匯編代碼,你可以用文本編輯器察看
-E
只激活預(yù)處理,這個不生成文件,你需要把它重定向到一個輸出文件里
面.
例子用法:
gcc -E hello.c > pianoapan.txt
gcc -E hello.c | more
慢慢看吧,一個hello word 也要與處理成800行的代碼
-o
制定目標(biāo)名稱,缺省的時候,gcc 編譯出來的文件是a.out,很難聽,如果 你和我有同感,改掉它,哈哈
例子用法
gcc -o hello.exe hello.c (哦,windows用習(xí)慣了)
gcc -o hello.asm -S hello.c
-ansi
關(guān)閉gnu c中與ansi c不兼容的特性,激活ansi c的專有特性(包括禁止一
些asm inline typeof關(guān)鍵字,以及UNIX,vax等預(yù)處理宏,
-Wall
-O0
-O1
-O2
-O3
編譯器的優(yōu)化選項的4個級別,-O0表示沒有優(yōu)化,-O1為缺省值,-O3優(yōu)化級別最
高
-g
只是編譯器,在編譯的時候,產(chǎn)生調(diào)試信息。
-llibrary
制定編譯的時候使用的庫
例子用法
gcc -lcurses hello.c
使用ncurses庫編譯程序
-Ldir
制定編譯的時候,搜索庫的路徑。比如你自己的庫,可以用它制定目錄,不然
編譯器將只在標(biāo)準(zhǔn)庫的目錄找。這個dir就是目錄的名稱。
-gstabs
此選項以stabs格式聲稱調(diào)試信息,但是不包括gdb調(diào)試信息.
-gstabs+
此選項以stabs格式聲稱調(diào)試信息,并且包含僅供gdb使用的額外調(diào)試信息.
-ggdb
此選項將盡可能的生成gdb的可以使用的調(diào)試信息.
-static 此選項將禁止使用動態(tài)庫,所以,編譯出來的東西,一般都很大,也不需要什么 動態(tài)連接庫,就可以運行.
-share 此選項將盡量使用動態(tài)庫,所以生成文件比較小,但是需要系統(tǒng)由動態(tài)庫.
-traditional 試圖讓編譯器支持傳統(tǒng)的C語言特性
-fno-asm
此選項實現(xiàn)ansi選項的功能的一部分,它禁止將asm,inline和typeof用作
關(guān)鍵字。
-fno-strict-prototype
只對g++起作用,使用這個選項,g++將對不帶參數(shù)的函數(shù),都認(rèn)為是沒有顯式
的對參數(shù)的個數(shù)和類型說明,而不是沒有參數(shù).
而gcc無論是否使用這個參數(shù),都將對沒有帶參數(shù)的函數(shù),認(rèn)為城沒有顯式說
明的類型
-fthis-is-varialble
就是向傳統(tǒng)c++看齊,可以使用this當(dāng)一般變量使用.
-fcond-mismatch
允許條件表達(dá)式的第二和第三參數(shù)類型不匹配,表達(dá)式的值將為void類型
-funsigned-char
-fno-signed-char
-fsigned-char
-fno-unsigned-char
這四個參數(shù)是對char類型進(jìn)行設(shè)置,決定將char類型設(shè)置成unsigned char(前
兩個參數(shù))或者 signed char(后兩個參數(shù))
-include file
包含某個代碼,簡單來說,就是便以某個文件,需要另一個文件的時候,就可以
用它設(shè)定,功能就相當(dāng)于在代碼中使用#include
例子用法:
gcc hello.c -include /root/pianopan.h
-imacros file
將file文件的宏,擴展到gcc/g++的輸入文件,宏定義本身并不出現(xiàn)在輸入文件
中
-Dmacro
相當(dāng)于C語言中的#define macro
-Dmacro=defn
相當(dāng)于C語言中的#define macro=defn
-Umacro
相當(dāng)于C語言中的#undef macro
-undef
取消對任何非標(biāo)準(zhǔn)宏的定義
-Idir
在你是用#include"file"的時候,gcc/g++會先在當(dāng)前目錄查找你所制定的頭
文件,如果沒有找到,他回到缺省的頭文件目錄找,如果使用-I制定了目錄,他
回先在你所制定的目錄查找,然后再按常規(guī)的順序去找.
對于#include,gcc/g++會到-I制定的目錄查找,查找不到,然后將到系
統(tǒng)的缺省的頭文件目錄查找
-I-
就是取消前一個參數(shù)的功能,所以一般在-Idir之后使用
-idirafter dir
在-I的目錄里面查找失敗,講到這個目錄里面查找.
-iprefix prefix
-iwithprefix dir
一般一起使用,當(dāng)-I的目錄查找失敗,會到prefix+dir下查找
-nostdinc
使編譯器不再系統(tǒng)缺省的頭文件目錄里面找頭文件,一般和-I聯(lián)合使用,明確
限定頭文件的位置
-nostdin C++
規(guī)定不在g++指定的標(biāo)準(zhǔn)路經(jīng)中搜索,但仍在其他路徑中搜索,.此選項在創(chuàng)建
libg++庫使用
-C
在預(yù)處理的時候,不刪除注釋信息,一般和-E使用,有時候分析程序,用這個很
方便的
-M
生成文件關(guān)聯(lián)的信息。包含目標(biāo)文件所依賴的所有源代碼
你可以用gcc -M hello.c來測試一下,很簡單。
-MM
和上面的那個一樣,但是它將忽略由#include造成的依賴關(guān)系。
-MD
和-M相同,但是輸出將導(dǎo)入到.d的文件里面
-MMD
和-MM相同,但是輸出將導(dǎo)入到.d的文件里面
-Wa,option
此選項傳遞option給匯編程序;如果option中間有逗號,就將option分成多個選
項,然后傳遞給會匯編程序
-Wl.option
此選項傳遞option給連接程序;如果option中間有逗號,就將option分成多個選 項,然后傳遞給會連接程序.
-x language filename設(shè)定文件所使用的語言,使后綴名無效,對以后的多個有效.也就是根據(jù)約定C語言的后綴名稱是.c的,而C++的后綴名是.C或者.cpp,如果你很個性,決定你的C代碼文件的后綴名是.pig 哈哈,那你就要用這個參數(shù),這個參數(shù)對他后面的文件名都起作用,除非到了下一個參數(shù)的使用??梢允褂玫膮?shù)嗎有下面的這些 `c', `objective-c', `c-header', `c++', `cpp-output',`assembler', and `assembler-with-cpp'. 看到英文,應(yīng)該可以理解的。 例子用法: gcc -x chello.pig
-x none filename關(guān)掉上一個選項,也就是讓gcc根據(jù)文件名后綴,自動識別文件類型 例子用法: gcc -x c hello.pig -x nonehello2.c
-pipe 使用管道代替編譯中臨時文件,在使用非gnu匯編工具的時候,可能有些問題gcc -pipe -o hello.exe hello.c
-funsigned-char -fno-signed-char -fsigned-char-fno-unsigned-char 這四個參數(shù)是對char類型進(jìn)行設(shè)置,決定將char類型設(shè)置成unsigned char(前兩個參數(shù))或者 signed char(后兩個參數(shù))