1. 程式人生 > >gcc編譯c++程式(轉載)

gcc編譯c++程式(轉載)

單個原始檔生成可執行程式

下面是一個儲存在檔案 helloworld.cpp 中一個簡單的 C++ 程式的程式碼: 

/* helloworld.cpp */
#include <iostream>
int main(int argc,char *argv[])
{
    std::cout << "hello, world" << std::endl;
    return(0);
}
程式使用定義在標頭檔案 iostream 中的 cout,向標準輸出寫入一個簡單的字串。該程式碼可用以下命令編譯為可執行檔案: 
$  g++ helloworld.cpp

編譯器 g++ 通過檢查命令列中指定的檔案的字尾名可識別其為 C++原始碼檔案。編譯器預設的動作:編譯原始碼檔案生成物件檔案(object file),連結物件檔案和 libstdc++ 庫中的函式得到可執行程式。然後刪除物件檔案。由於命令列中未指定可執行程式的檔名,編譯器採用預設的 a.out。程式可以這樣來執行: 
$ ./a.out
hello, world
更普遍的做法是通過 -o 選項指定可執行程式的檔名。下面的命令將產生名為 helloworld 的可執行檔案: 
$ g++ helloworld.cpp -o helloworld
在命令列中輸入程式名可使之執行: 
$ ./helloworld
hello, world
程式 g++ 是將 gcc 預設語言設為 C++ 的一個特殊的版本,連結時它自動使用 C++ 標準庫而不用 C 標準庫。通過遵循原始碼的命名規範並指定對應庫的名字,用 gcc 來編譯連結 C++ 程式是可行的,如下例所示: 
$ gcc helloworld.cpp -lstdc++ -o helloworld
選項 -l (ell) 通過新增字首 lib 和字尾 .a 將跟隨它的名字變換為庫的名字 libstdc++.a。而後它在標準庫路徑中查詢該庫。gcc 的編譯過程和輸出檔案與 g++ 是完全相同的。 
在大多數系統中,GCC 安裝時會安裝一名為 c++ 的程式。如果被安裝,它和 g++ 是等同,如下例所示,用法也一致: 
$ c++ helloworld.cpp -o helloworld


多個原始檔生成可執行程式
如果多於一個的原始碼檔案在 g++ 命令中指定,它們都將被編譯並被連結成一個單一的可執行檔案。下面是一個名為 speak.h 的標頭檔案;它包含一個僅含有一個函式的類的定義: 

    /* speak.h */
    #include <iostream>
    class Speak
    {
        public:
            void sayHello(const char *);
    };
下面列出的是檔案 speak.cpp 的內容:包含 sayHello() 函式的函式體: 
/* speak.cpp */
#include "speak.h"
void Speak::sayHello(const char *str)
{
    std::cout << "Hello " << str << "\n";
}
檔案 hellospeak.cpp 內是一個使用 Speak 類的程式: 
    /* hellospeak.cpp */
    #include "speak.h"
    int main(int argc,char *argv[])
    {
        Speak speak;
        speak.sayHello("world");
        return(0);
    }
下面這條命令將上述兩個原始碼檔案編譯連結成一個單一的可執行程式: 
$ g++ hellospeak.cpp speak.cpp -o hellospeak
PS:這裡說一下為什麼在命令中沒有提到“speak.h“該檔案(原因是:在“speak.cpp“中包含有”#include"speak.h"“這句程式碼,它的意思是搜尋系統標頭檔案目錄之前將先在當前目錄中搜索檔案“speak.h“。而”speak.h“正在該目錄中,不用再在命令中指定了)。 
原始檔生成物件檔案
選項 -c 用來告訴編譯器編譯原始碼但不要執行連結,輸出結果為物件檔案。檔案預設名與原始碼檔名相同,只是將其後綴變為 .o。例如,下面的命令將編譯原始碼檔案 hellospeak.cpp 並生成物件檔案 hellospeak.o: 
$ g++ -c hellospeak.cpp
命令 g++ 也能識別 .o 檔案並將其作為輸入檔案傳遞給連結器。下列命令將編譯原始碼檔案為物件檔案並將其連結成單一的可執行程式: 
$ g++ -c hellospeak.cpp 
$ g++ -c speak.cpp 
$ g++ hellospeak.o speak.o -o hellospeak
選項 -o 不僅僅能用來命名可執行檔案。它也用來命名編譯器輸出的其他檔案。例如:除了中間的物件檔案有不同的名字外,下列命令生將生成和上面完全相同的可執行檔案: 
$ g++ -c hellospeak.cpp -o hspk1.o 
$ g++ -c speak.cpp -o hspk2.o 
$ g++ hspk1.o hspk2.o -o hellospeak

編譯預處理
選項 -E 使 g++ 將原始碼用編譯前處理器處理後不再執行其他動作。下面的命令預處理原始碼檔案 helloworld.cpp 並將結果顯示在標準輸出中: 
本文前面所列出的 helloworld.cpp 的原始碼,僅僅有六行,而且該程式除了顯示一行文字外什麼都不做,但是,預處理後的版本將超過 1200 行。這主要是因為標頭檔案 iostream 被包含進來,而且它又包含了其他的標頭檔案,除此之外,還有若干個處理輸入和輸出的類的定義。 
預處理過的檔案的 GCC 字尾為 .ii,它可以通過 -o 選項來生成,例如: 
$ gcc -E helloworld.cpp -o helloworld.ii
生成彙編程式碼
選項 -S 指示編譯器將程式編譯成組合語言,輸出組合語言程式碼而後結束。下面的命令將由 C++ 原始碼檔案生成組合語言檔案 helloworld.s: 
$ g++ -S helloworld.cpp
生成的組合語言依賴於編譯器的目標平臺。 
建立靜態庫 
靜態庫是編譯器生成的一系列物件檔案的集合。連結一個程式時用庫中的物件檔案還是目錄中的物件檔案都是一樣的。庫中的成員包括普通函式,類定義,類的物件例項等等。靜態庫的另一個名字叫歸檔檔案(archive),管理這種歸檔檔案的工具叫 ar 。 
在下面的例子中,我們先建立兩個物件模組,然後用其生成靜態庫。 
標頭檔案 say.h 包含函式 sayHello() 的原型和類 Say 的定義: 
/* say.h */
#include <iostream>
void sayhello(void);
class Say {
    private:
        char *string;
    public:
        Say(char *str)
        {
            string = str;
        }
        void sayThis(const char *str)
        {
            std::cout << str << " from a static library\n";
        }
        void sayString(void);
};
下面是檔案 say.cpp 是我們要加入到靜態庫中的兩個物件檔案之一的原始碼。它包含 Say 類中 sayString() 函式的定義體;類 Say 的一個例項 librarysay 的宣告也包含在內: 
    /* say.cpp */
    #include "say.h"
    void Say::sayString()
    {
        std::cout << string << "\n";
    }
    Say librarysay("Library instance of Say");

原始碼檔案 sayhello.cpp 是我們要加入到靜態庫中的第二個物件檔案的原始碼。它包含函式 sayhello() 的定義: 

    /* sayhello.cpp */
    #include "say.h"
    void sayhello()
    {
        std::cout << "hello from a static library\n";
    }
下面的命令序列將原始碼檔案編譯成物件檔案,命令 ar 將其存進庫中: 
$ g++ -c sayhello.cpp
$ g++ -c say.cpp
$ ar -r libsay.a sayhello.o say.o

程式 ar 配合引數 -r 建立一個新庫 libsay.a 並將命令列中列出的物件檔案插入。採用這種方法,如果庫不存在的話,引數 -r 將建立一個新的庫,而如果庫存在的話,將用新的模組替換原來的模組。 
下面是主程式 saymain.cpp,它呼叫庫 libsay.a 中的程式碼: 
/* saymain.cpp */
#include "say.h"
int main(int argc,char *argv[])
{
    extern Say librarysay;
    Say localsay = Say("Local instance of Say");
    sayhello();
    librarysay.sayThis("howdy");
    librarysay.sayString();
    localsay.sayString();
    return(0);
}
該程式可以下面的命令來編譯和連結: 
$ g++ saymain.cpp libsay.a -o saymain
程式執行時,產生以下輸出: 
hello from a static library
howdy from a static library
Library instance of SayLocal instance of Say

動態連結庫的編寫和使用

 

這裡我們用到4個檔案,它們分別為:SoDemoTest.h、one.cpp、two.cpp、three.cpp。它們的內容如下: 

SoDemoTest.h
ifndef __SO_DEMO_TEST_HEADER__   
#define __SO_DEMO_TEST_HEADER__   
#include <iostream>   
using namespace std;  
void one();  
void two();  
void three();  
#endif  

one.cpp

    #include "SoDemoTest.h"   
    void one()  
    {  
        cout << "call one() function." << endl;  
    }  

two.cpp

#include "SoDemoTest.h"   
void two()  
{  
    cout << "call two() function." << endl;  
}  

three.cpp

#include "SoDemoTest.h"   
void three()  
{  
    cout << "call three() function." << endl;  
}  
將這幾個檔案編譯成動態庫libtest.so。編譯命令如下:

2、動態庫的連線

main.cpp

#include "SoDemoTest.h"   
int main()  
{  
    one();  
    two();  
    three();  
    return 0;  
}  

將main.cpp與libtest.so連結成一個可執行檔案main。命令如下:

$ g++ main.cpp -L. -ltest -o main

測試可執行程式main是否已經連結的動態庫libtest.so,如果列出了libtest.so,那麼就說明正常連結了。可以執行以下命令:

$ ldd main

執行main可以看看main是否呼叫了動態連結庫中的函式。

-shared 該選項指定生成動態連線庫(讓聯結器生成T型別的匯出符號表,有時候也生成弱連線W型別的匯出符號),不用該標誌外部程式無法連線。

相當於一個可執行檔案

-fPIC:表示編譯為位置獨立的程式碼,不用此選項的話編譯後的程式碼是位置相關的所以動態載入時是通過程式碼拷貝的方式來滿足不同程序的需要,

而不能達到真正程式碼段共享的目的。

-ltest:編譯器查詢動態連線庫時有隱含的命名規則,即在給出的名字前面加上lib,後面加上.so來確定庫的名稱

error while loading shared libraries: libtest.so: cannot open shared object file: No such file or directory 出現這個錯誤時候,就要設定

 

4、注意的問題

呼叫動態庫的時候有幾個問題會經常碰到,有時,明明已經將庫的標頭檔案所在目錄 通過 “-I” include進來了,庫所在檔案通過 “-L”引數引導,並指定了“-l”的庫名,但通過ldd命令察看時,就是死活找不到你指定連結的so檔案,這時你要作的就是通過修改 LD_LIBRARY_PATH或者/etc/ld.so.conf檔案來指定動態庫的目錄。通常這樣做就可以解決庫無法連結的問題了。

 

動態呼叫SO檔案

 

1.製作so檔案:libadd_c.so

 

add.c:

int add(int a, int b)
{
    return a+b;
}

編譯:

gcc -shared -fpic -lm -ldl -o libadd_c.so add.c

2.編寫測試函式

test.cpp

    #include <stdio.h>  
    #include <dlfcn.h>  
    #include <stdlib.h>  
    #include <iostream>  
    using namespace std;  
    int main()  
    {  
        int a = 0;  
          
        void *handle = dlopen("./libadd_c.so", RTLD_LAZY);  
          
        if(!handle)  
        {  
            printf("open lib error\n");  
            cout<<dlerror()<<endl;  
            return -1;  
        }  
          
        typedef int (*add_t)(int a, int b);  
        add_t add = (add_t) dlsym(handle, "add");  
        if(!add)  
        {  
            cout<<dlerror()<<endl;  
            dlclose(handle);  
            return -1;  
        }  
          
          
        a = add(3, 4);  
        printf("a = %d\n",a);  
        dlclose(handle);  
        return 0;  
    }  

編譯:

g++ test.cpp -ldl -o test

 3.執行

./test

 

介紹一下上面用到的介面函式

1) dlopen

函式原型:void *dlopen(const char *libname,int flag);

功能描述:dlopen必須在dlerror,dlsym和dlclose之前呼叫,表示要將庫裝載到記憶體,準備使用。如果要裝載的庫依賴於其它庫,必須首先裝載依賴庫。如果dlopen操作失敗,返回NULL值;如果庫已經被裝載過,則dlopen會返回同樣的控制代碼。

引數中的libname一般是庫的全路徑,這樣dlopen會直接裝載該檔案;如果只是指定了庫名稱,在dlopen會按照下面的機制去搜尋:

a.根據環境變數LD_LIBRARY_PATH查詢

b.根據/etc/ld.so.cache查詢

c.查詢依次在/lib和/usr/lib目錄查詢。

flag引數表示處理未定義函式的方式,可以使用RTLD_LAZY或RTLD_NOW。RTLD_LAZY表示暫時不去處理未定義函式,先把庫裝載到記憶體,等用到沒定義的函式再說;RTLD_NOW表示馬上檢查是否存在未定義的函式,若存在,則dlopen以失敗告終。

2) dlerror

函式原型:char *dlerror(void);

功能描述:dlerror可以獲得最近一次dlopen,dlsym或dlclose操作的錯誤資訊,返回NULL表示無錯誤。dlerror在返回錯誤資訊的同時,也會清除錯誤資訊。

3) dlsym

函式原型:void *dlsym(void *handle,const char *symbol);

功能描述:在dlopen之後,庫被裝載到記憶體。dlsym可以獲得指定函式(symbol)在記憶體中的位置(指標)。如果找不到指定函式,則dlsym會返回NULL值。但判斷函式是否存在最好的方法是使用dlerror函式,

4) dlclose

函式原型:int dlclose(void *);

功能描述:將已經裝載的庫控制代碼減一,如果控制代碼減至零,則該庫會被解除安裝。如果存在解構函式,則在dlclose之後,解構函式會被呼叫。

好了,現在來編譯打包,命令如下:

$ g++ -shared -fPIC -o libhello.so hello.cpp
$ g++ main.cpp -ldl

在上面dlopen函式中,看到我們傳的第一個引數並沒有指定路徑,只給出了庫的名稱。那是因為已經在環境變數LD_LIBRARY_PATH中指定了 ./ 目錄,如下圖所示。

如果你想放在其他目錄,修改該環境變數即可。

 

gcc/g++ 動態編譯和連結問題

-l引數和-L引數:
-l引數就是用來指定程式要連結的庫,-l引數緊接著就是庫名,那麼庫名跟真正的庫檔名有什麼關係呢?就拿數學庫來說,他的庫名是m,他的庫檔名是libm.so,很容易看出,把庫檔名的頭lib和尾.so去掉就是庫名了。
好了現在我們知道怎麼得到庫名,當我們自已要用到一個第三方提供的庫名字libtest.so,那麼我們只要把libtest.so拷貝到/usr/lib裡,編譯時加上-ltest引數,我們就能用上libtest.so庫了(當然要用libtest.so庫裡的函式,我們還需要與 libtest.so配套的標頭檔案)。
放在/lib和/usr/lib和/usr/local/lib裡的庫直接用-l引數就能連結了,但如果庫檔案沒放在這三個目錄裡,而是放在其他目錄裡,這時我們只用-l引數的話,連結還是會出錯,出錯資訊大概是:“/usr/bin/ld: cannot find -lxxx”,也就是連結程式ld在那3個目錄裡找不到libxxx.so,這時另外一個引數-L就派上用場了,比如常用的X11的庫,它在/usr /X11R6/lib目錄下,我們編譯時就要用-L/usr/X11R6/lib -lX11引數,-L引數跟著的是庫檔案所在的目錄名。再比如我們把libtest.so放在/aaa/bbb/ccc目錄下,那連結引數就是-L /aaa/bbb/ccc -ltest。
另外,大部分libxxxx.so只是一個連結,以RH9為例,比如libm.so它連結到/lib/libm.so.x,/lib/libm.so.6又連結到/lib/libm-2.3.2.so,如果沒有這樣的連結,還是會出錯,因為ld只會找libxxxx.so,所以如果你要用到xxxx庫,而只有libxxxx.so.x或者libxxxx-x.x.x.so,做一個連結就可以了ln -s libxxxx-x.x.x.so libxxxx.so。
手工來寫連結引數總是很麻煩的,還好很多庫開發包提供了生成連結引數的程式,名字一般叫xxxx-config,一般放在/usr/bin目錄下,比如gtk1.2的連結引數生成程式是gtk-config,執行gtk-config --libs就能得到以下輸出"-L/usr/lib -L/usr/X11R6/lib -lgtk -lgdk -rdynamic-lgmodule -lglib -ldl -lXi -lXext -lX11 -lm",這就是編譯一個gtk1.2程式所需的gtk連結引數,xxx-config除了--libs引數外還有一個引數是--cflags用來生成標頭檔案包含目錄的,也就是-I引數,在下面我們將會講到。你可以試試執行gtk-config --libs --cflags,看看輸出結果。
現在的問題就是怎樣用這些輸出結果了,最笨的方法就是複製貼上或者照抄,聰明的辦法是在編譯命令列里加入這個`xxxx-config --libs --cflags`,比如編譯一個gtk程式:gcc gtktest.c `gtk-config --libs --cflags`這樣就差不多了。注意`不是單引號,而是1鍵左邊那個鍵。


-include和-I引數:

-include用來包含標頭檔案,但一般情況下包含標頭檔案都在原始碼裡用#include xxxxxx實現,-include引數很少用。-I引數是用來指定標頭檔案目錄,/usr/include目錄一般是不用指定的,gcc知道去那裡找,但是如果標頭檔案不在/usr/include裡我們就要用-I引數指定了,比如標頭檔案放在/myinclude目錄裡,那編譯命令列就要加上-I/myinclude引數了,如果不加你會得到一個"xxxx.h: No such file or directory"的錯誤。-I引數可以用相對路徑,比如標頭檔案在當前目錄,可以用-I.來指定。

 

 

gcc & g++現在是gnu中最主要和最流行的c & c++編譯器 。
g++
c++的命令,以.cpp為主,對於c語言字尾名一般為.c。這時候命令換做gcc即可。其實是無關緊要的。
其實編譯器是根據gcc還是g++來確定是按照C標準還是C++標準編譯連結。

下面以Test.cpp為例:

命令: g++ Test.cpp
功能:生成預設為a.exe的檔案,這個過程包含了編譯和連結。
再說下-o命令,-o命令表示輸出的意思,gcc/g++命令是非常靈活的,你不指定輸出的檔名的時候預設生成的是.exe檔案。
你要輸出Test.exe的話可以用:g++ -o Test.exe Test.cpp-o命令是輸出的意思,這樣就輸出了Test.exe

 

gcc/g++在執行編譯工作的時候,總共需要以下幾步:

    /************************* 
            Test.cpp 
        *************************/  
        #include <IOSTREAM>  
      
        static int t = 1;  
        #define T 9  
      
        using namespace std;  
      
        typedef int Status;  
      
        int main()  
        {  
            Status i = 1;  
            cout << T * i << endl; //Test Cout  
            return 0;  
        }  

1.預處理,生成.i的檔案[前處理器cpp]

命令:g++ -E Test.cpp > Test.i 

功能:輸出預處理後的檔案,linux下以.i為字尾名。只啟用預處理,這個不生成檔案,你需要把它重定向到一個輸出檔案裡 。

這一步主要做了這些事情:巨集的替換,還有註釋的消除,還有找到相關的庫檔案。用編輯器開啟Test.i會發現有很多很多程式碼,

你只需要看最後部分就會發現,預處理做了巨集的替換,還有註釋的消除,可以理解為無關程式碼的清除。下面是Test.i檔案的最後部分,

可以看見巨集的替換和註釋的消除。

    # 5 "Test.cpp" 2  
      
    static int t = 1;  
      
    using namespace std;  
      
    typedef int Status;  
      
    int main()  
    {  
     Status i = 1;  
     cout << 9 * i << endl;  
     return 0;  
    }  

2.將預處理後的檔案不轉換成組合語言,生成檔案.s[編譯器egcs]

命令:g++ -S Test.cpp
功能:會生成Test.s檔案,.s檔案表示是彙編檔案,用編輯器開啟就都是彙編指令。

3.有彙編變為目的碼(機器程式碼)生成.o的檔案[彙編器as]

命令:g++ -c Test.cpp 
功能:.o是GCC生成的目標檔案,除非你是做編譯器和聯結器除錯開發的,否則開啟這種.o沒有任何意義。二進位制機器碼一般人也讀不了。

4.連線目的碼,生成可執行程式[連結器ld]

命令:g++ Test.o -L F:\vs2008\VC\include\iostream
功能:將.o檔案與所需的庫檔案連結整合形成.exe檔案,這就是可執行檔案。-L 表示連結,這裡我後面寫的是絕對路徑,相對各人電腦不同


在上面各個步驟中你可以用-o命令輸出你自己想要的各種名字。比如最後一個命令,用下面的輸出Test.exe
你可以g++ Test.o -o Test.exe -L F:\vs2008\VC\include\iostream


====================================================================================================

 

gcc and g++分別是gnu的c & c++編譯器 gcc/g++在執行編譯工作的時候,總共需要4步

1.預處理,生成.i的檔案[前處理器cpp]

2.將預處理後的檔案轉換成組合語言,生成檔案.s[編譯器egcs]

3.由彙編變為目的碼(機器程式碼)生成.o的檔案[彙編器as]

4.連線目的碼,生成可執行程式[連結器ld]

[引數詳解]

-x language filename

 設定檔案所使用的語言,使字尾名無效,對以後的多個有效.也就是根據約定C語言的字尾名稱是.c的,而C++的字尾名是.C或者.cpp,如果你很個性,

   決定你的C程式碼檔案的字尾名是.pig 哈哈,那你就要用這個引數,這個引數對他後面的檔名都起作用,除非到了下一個引數的使用。

  可以使用的引數嗎有下面的這些

  `c', `objective-c', `c-header', `c++', `cpp-output', `assembler', and `assembler-with-cpp'.

  看到英文,應該可以理解的。

  例子用法:

  gcc -x c hello.pig

  

-x none filename

  關掉上一個選項,也就是讓gcc根據檔名字尾,自動識別檔案型別

  例子用法:

  gcc -x c hello.pig -x none hello2.c

  

-c

  只啟用預處理,編譯,和彙編,也就是他只把程式做成obj檔案

  例子用法:

  gcc -c hello.c

  他將生成.o的obj檔案

-S

  只啟用預處理和編譯,就是指把檔案編譯成為彙編程式碼。

  例子用法

  gcc -S hello.c

  他將生成.s的彙編程式碼,你可以用文字編輯器察看

-E

  只啟用預處理,這個不生成檔案,你需要把它重定向到一個輸出檔案裡面.

  例子用法:

  gcc -E hello.c > pianoapan.txt

  gcc -E hello.c | more

  慢慢看吧,一個hello word 也要與處理成800行的程式碼

-o

  制定目標名稱,預設的時候,gcc 編譯出來的檔案是a.out,很難聽,如果你和我有同感,改掉它,哈哈

  例子用法

  gcc -o hello.exe hello.c (哦,windows用習慣了)

  gcc -o hello.asm -S hello.c

-pipe

  使用管道代替編譯中臨時檔案,在使用非gnu彙編工具的時候,可能有些問題

  gcc -pipe -o hello.exe hello.c

-ansi

  關閉gnu c中與ansi c不相容的特性,啟用ansi c的專有特性(包括禁止一些asm inline typeof關鍵字,以及UNIX,vax等預處理巨集,

-fno-asm

  此選項實現ansi選項的功能的一部分,它禁止將asm,inline和typeof用作關鍵字。     

-fno-strict-prototype

  只對g++起作用,使用這個選項,g++將對不帶引數的函式,都認為是沒有顯式的對引數的個數和型別說明,而不是沒有引數.

  而gcc無論是否使用這個引數,都將對沒有帶引數的函式,認為城沒有顯式說明的型別

  

-fthis-is-varialble

  就是向傳統c++看齊,可以使用this當一般變數使用.

  

-fcond-mismatch

  允許條件表示式的第二和第三引數型別不匹配,表示式的值將為void型別

  

-funsigned-char

-fno-signed-char

-fsigned-char

-fno-unsigned-char

  這四個引數是對char型別進行設