Using GCC’s C++ Compiler

本文討論GNU編譯器集合(GCC)中的C++編譯器(g++)的典型用法,主要是指命令行選項的構造。GCC的C++編譯器正常安裝後,可以使用g++或c++命令執行。
GCC Option Refresher
本節回顧GCC的C編譯器的基本使用方法。
g++編譯器的選項可以是單字符,比如-o,也可以多字符,比如-ansi。所以你不可以把多個單字符選項合寫到一起,這和許多其他GNU和UNIX下的程序不同。例如,多字符選項-pg不表示2個單字符選項-p -g。選項-pg表示在最終的2進制文件裏生成額外的代碼,用來輸出GNU code profiler的信息gprof;而選項-p -g則表示在目標2進制文件裏生成額外的代碼,用來產生prof code profiler需要的信息(-p),並在目標里加入調試信息(-g)。
既然g++把多字符的選項進行了區分,你就可以隨意安排各個選項的順序了。比如:
g++ -pg -fno-strength-reduce -g myprog.c -o myprog
g++ myprog.c -o myprog -g -fno-strength-reduce -pg
是一樣的。
一般情況下,這些選項的順序是無所謂的。但是在有些情況下,順序會變得重要,比如你多次使用同一類的選項。舉個例子,-I選項指定了搜索include文件的目錄,如果你用-I指定了多個目錄,gcc會按照你指定目錄的順序搜索需要的文件。
用g++編譯單個源文件myprog.cc很簡單,只要把文件名當參數傳給g++就行了。
$ g++ myprog.cc
$ ls -l
-rwxr-xr-x 1 wvh users 13644 Oct 5 16:17 a.out
-rw-r--r-- 1 wvh users 220 Oct 5 16:17 myprog.cc
默認情況下,UNIX和LINUX操作系統生成的目標文件是當前目錄下的a.out,只要輸入./a.out就可以執行。在Cygwin系統下,你得到的是a.exe,通過輸入./a或者./a.exe都可以執行。
要定義g++的輸出文件名,使用-o選項如下所示:
$ g++ myprog.cc -o runme
$ ls -l
-rw-r--r-- 1 wvh users 220 Oct 5 16:17 myprog.cc
-rwxr-xr-x 1 wvh users 13644 Oct 5 16:28 runme
如果編譯多個源文件,只要在命令行裏列出它們就行了,如下所示,最終產生的輸出文件是showdate:
$ g++ showdate.cc helper.cc –o showdate
如果你想先編譯這些源文件,最後再把它們鏈接成一個2進制文件,可以用-c選項,那麼g++就只產生object文件,如下所示:
$ g++ -c showdate.cc
$ g++ -c helper.cc
$ g++ showdate.o helper.o –o showdate
$ ls -l
total 124
-rw-r--r-- 1 wvh users 210 Oct 5 12:42 helper.cc
-rw-r--r-- 1 wvh users 45 Oct 5 12:29 helper.h
-rw-r--r-- 1 wvh users 1104 Oct 5 13:50 helper.o
-rwxr-xr-x 1 wvh users 13891 Oct 5 13:51 showdate
-rw-r--r-- 1 wvh users 208 Oct 5 12:44 showdate.cc
-rw-r--r-- 1 wvh users 1008 Oct 5 13:50 showdate.o
注意
所有的GCC編譯器都是通過文件的後綴名來判斷文件類型的,然後選擇應該進行的操作(比如,後綴名爲.o的文件只需要進行鏈接),文件類型到操作的映射記錄在GCC的specs文件裏。在GCC版本4以前,specs文件是標準文本文件,可以用任何文本編輯器修改;但是GCC版本4以後specs文件是內建文件,必須要進行解壓才能修改。
很顯然,當你的項目文件稍微多點,使用命令行來編譯就不可接受了,特別是還要加上搜索目錄、優化選項和其他g++選項。解決的方案就是make,不過本文並不討論它。
C++源文件擴展名
前面說過所有GCC編譯器都通過文件後綴名來決定採用的操作。下表列出了g++認識的文件類型和相應的操作。
Suffix     Operation
.C         C++ source code to preprocess.
.cc        C++ source code to preprocess. This is the standard extension for C++ source files.
.cpp       C++ source code to preprocess.
.cxx       C++ source code to preprocess
.ii        C++ source code not to preprocess.
如果一個文件的後綴名未知,那麼就當成object文件進行鏈接。這並不是說你只能使用上表列出的文件名後綴來區分源代碼文件和其他文件,你可以用-x lang選項指定一個或多個輸入文件的代碼類型,不使用標準的文件名後綴規則。lang參數指定代碼的類型;對於C++,輸入文件可以是c++(標準的C++源文件)或c++-cpp-output(已經被預處理過的C++源文件,不需再進行預處理)。
注意
當GCC編譯器遇到上表列出的文件後綴,它會當成C++文件。但是,有些GCC編譯器(比如gcc)不能處理C++程序裏很複雜的依賴關係,比如複雜的類庫,於是編譯失敗。所以你應該用g++(或c++)來編譯C++程序。
GCCC++編譯器的命令行選項
許多命令行選項對於GCC編譯器家族都是通用的,下表只列出g++專有的命令行參數。
Option
Description
-fabi-version=n
指定編譯代碼需要符合的C++ ABI(application binary interface)版本。對於GCC版本3.4及更高,默認的ABI版本是2。
-fcheck-new
保證new操作返回的指針爲非空。
-fconserve-space
把全局變量的初始化操作延遲到運行的時候,common segment裏的全局變量不初始化,這樣減少可執行文件的大小。
-fdollars-in-identifiers
允許標識符裏出現$符號(默認)。
-fms-extensions
使g++忽略Microsoft Foundation Classes (MFC)中非標準用法的警告信息。
-fno-access-control
禁止訪問檢查
-fno-const-strings
強制g++把字符串常量的類型定義成char *,而不管ISO C++標準是否要求是const char *。
-fno-elide-constructors
強制g++總是調用copy構造函數,即使在用臨時對象初始化另一個同類型對象的時候。
-fno-enforce-eh-specs
禁止在運行時檢查異常處理違例。
-ffor-scope
對於for語句初始化部分申明的變量,限制其作用域是for循環以內。你也可以用-fno-for-scope選項強制其作用域爲下一個‘}’之前,雖然這和ISO標準衝突,但是舊版本g++和許多其他傳統的C++編譯器都是這樣做的。
-fms-extensions
禁止對Microsoft Foundation Classes代碼的不必要的警告。
-fno-gnu-keywords
禁止把typeof作爲一個關鍵字,這樣就可以用它作爲標識符使用,你仍可以使用__typeof__關鍵字來代替它。該選項被包含在了-ansi選項裏面。
-fno-implement-inlines
Saves space by not creating out-of-line copies of inline functions controlled by #pragma statements. Using this option will generate linker errors if the such functions are not inlined everywhere they are called
-fno-implicit-inline-templates
不創建隱含的模板實例以節省空間。(詳見-fno-implicit-templates)
-fno-implicit-templates
只創建外聯(非內聯)模板的顯式實例以節省空間。
-fno-nonansi-builtins
禁止使用非ANSI/ISO標準的內置屬性,包括ffs、alloca、_exit、index、bzero、conjf及其他相關的函數。
-fno-operator-names
禁止使用and、bitand、bitor、compl、not、or和xor關鍵字作爲對應操作符的同義詞。
-fno-optional-diags
禁止非標準的內部語法診斷,比如類中特殊的名字應該在何時使用各種不同的形式。
-fno-rtti
禁止給類的虛函數產生運行時類型信息(RTTI)
-fno-threadsafe-statics
使g++不產生用於線程安全檢查的代碼,這樣能減少代碼量,如果不需要線程安全的話。
-fno-weak
使g++不使用弱符合支持,即使鏈接器支持它。這個選擇用於g++測試的時候,其他時候請不要使用。
-fpermissive
把代碼的語法錯誤作爲警告,並繼續編譯進程。
-frepo
允許模板實例化在連接時自動進行。該選項包含了-fno-implicit-templates選項。
-fstats
編譯完成後顯示前端的統計信息。該選項一般只有g++開發人員使用。
-ftemplate-depth-n
保證模板實例化的遞歸深度不超過整數n
-fuse-cxa-atexit
註冊靜態對象的析構函數時,使用__cxa_atexit而不是atexit。
-fvisibility=value
(GCC 4.02或以後)使g++不導出ELF(Executable and Linking Format,Linux和Solaris等系統上默認的2進制文件格式)中用hidden標識的object模塊內或庫內的符號。該選項能減少目標文件大小,加快符號表的查找,從而改善運行性能。但是,該選項也會因爲不同的visibility等級而導致模塊間拋出異常發生問題,詳見後面的““Visibility Attributes and Pragmas for GCC C++ Libraries”一節。如果沒有使用該選項,那麼默認的visibility值是default,即導出所有目標文件和庫裏的符號。
-nostdinc++
禁止在C++的標準目錄裏搜索頭文件。
g++編譯器的其他一些C++選項處理優化、警告和代碼生成的任務,我們在其他章節裏討論。下表總結了專對C++的警告選項。
Option
Description
-Wabi
當編譯器生成的代碼和標準C++ ABI不兼容的時候發出警告。對於GCC版本3.4和更高,默認的ABI版本是2。
-Wctor-dtor-privacy
當一個類的所有構造函數和析構函數都是私有時發出警告。
-Weffc++
當出現不符合《Effective C++》(Scott Meyers,Addison-Wesley,2005,ISBN: 0-321-33487-6)風格的代碼時給出警告
-Wno-deprecated
使用已過時C++屬性和用法時不給出警告。
-Wno-non-template-friend
當非模板的友元函數定義在模板裏時不給出警告。In the C++ language template specification, a friend must declare or define a nontemplate function if the name of the friend is an unqualified identifier.
-Wno-pmf-conversions
當把一個指向類成員函數的指針隱式轉化成一般指針的時候不給出警告。
-Wnon-virtual-dtor
當一個類需要虛析構函數而又沒有申明虛析構函數的時候給出警告。該選項被包含在-Wall選項裏。
-Wold-style-cast
當在C++源代碼裏使用了傳統C語言風格的類型轉換方式時,給出警告。
-Woverloaded-virtual
當子類的函數申明覆蓋基類虛函數的時候給出警告。
-Wreorder
當類成員變量的初始化順序和申明順序不一致的時候給出警告。g++編譯器會自動記錄所有變量的正確初始化順序。該選項被包含在-Wall選項裏。
-Wsign-promo
當一個重載操作把一個有符號數值轉換成無符號數值的時候給出警告。在版本3.4及以前,g++對無符號類型進行了保護,但是這和C++標準不一致。
-Wstrict-null-sentinel
當用一個無類型的NULL作爲哨兵的時候發出警告。哨兵是指一個無效的輸入值,通常代表輸入的結束。此問題的原因是無類型的NULL在不同的編譯器實現裏有不同的大小,所以必須先轉化成固定的類型。
ABI Differences in g++ Versions
C++ ABI是一套API標準,定義了C++庫提供的數據類型、類、方法、頭文件等的接口和規範。對庫和目標文件來說,物理組織、參數傳遞方式和命名方式是很重要的,所以需要一個統一的接口,使編譯出來的C++程序與提供的庫的接口一致。這種一致性對語言特有的一些屬性更加重要,比如拋出異常和捕捉異常的時候。
從版本3開始的GNU C++編譯器,都遵循一個工業標準的C++ ABI規範,定義在http://www.codesourcery.com/cxx-abi/abi.html。雖然這個規範是爲64位Itanium定製的,但是它適用於任何平臺,並且已經作爲GNU/Linux和BSD系統的C++ ABI的實現。
版本3.4以前的g++使用ABI版本1,之後使用ABI版本2。不同ABI版本之間的程序和庫不能混用。如果你不確定自己g++的ABI版本,可以用g++ --version命令檢查g++的版本,或用一個僞編譯命令顯示ABI標識符,命令行如下:
g++ -E -dM - < /dev/null | awk '/GXX_ABI/ {print $3}'
如果顯示102,那麼就是版本1;如果顯示1002,就是版本2。如果你必須用到以前版本ABI的庫,那麼給g++加上選項-fabi-version=n,其中n就是你要兼容的ABI版本。這樣做只能算作權宜之計,把所有舊的代碼和庫更新到當前版本纔是最佳解決方案。
GNU C++ Implementation Details and Extensions
本文雖然不討論怎樣寫好C++程序,但是當你用GCC的C++編譯器編譯你的C++程序的時候,你可以從GCC的擴展中得到許多好處,包括編譯器自身的優勢和g++使用的標準C++庫libstdc++的優勢。本節提煉出最爲重要的一些擴展特性,並討論它們在C++規範和編譯器行爲方面的一些差異。
Attribute Definitions Specific to g++
作爲對visibility屬性(詳見於“Visibility Attributes and Pragmas for GCC C++ Libraries”)的補充,g++提供了2個額外的屬性,即init_priority(priority)和java_interface屬性。
The init_priority Attribute
該屬性允許用戶控制某個名字空間裏的對象的初始化順序。通常,對象的初始化順序是它們在某個代碼單元裏的定義順序。init_priority只有一個整型參數,值爲101到65535,越小表示優先級越大。比如,在下面的僞碼裏,類MyClass將比類YourClass先初始化:
class MyClass
{
};
class YourClass
{
__attribute__ ((visibility("default"))) void MyMethod();
};
要改變它們的初始化順序,你可以把代碼改成下面這樣:
class MyClass
{
__attribute__ ((init_priority(65535)));
};
class YourClass
{
__attribute__ ((init_priority(101)));
};
你只需要注意所使用的優先級數值的順序,具體使用了哪個數值則無所謂(即只要MyClass的優先級數值比YourClass大就行了,是不是65535101則無所謂)。
The java_interface Attribute
該屬性通知g++某個類是一個Java接口類,並只能在標識了extern “Java”的模塊內使用。調用這個類的函數使用的是GCC Java編譯器的接口表機制(interface table mechanism),而不是通常的C++虛函數表機制(virtual function table mechanism)。
提示
記住,Java的運行時環境需要更多的初始化工作。當你混合使用C++和Java代碼時,最好用Java寫主程序,這樣能保證調用Java函數前初始化工作已經做足了。
C++ Template Instantiation in g++
模板是C++最有用和最有趣的特性之一,能減少重複代碼,提高複用率,簡化調試和代碼維護工作。模板也有利於編譯時的類型檢查,比如,使用了模板就不用再傳遞void指針,因爲你可以把模板參數實例化成任何需要的類型。
g++通過增加3個功能擴展了標準的ISO模板定義:
支持使用extern關鍵詞對實例化類型進行前置申明;
The ability to instantiate the support data required by the compiler for a named template class without actually instantiating it by using the inline keyword
The ability to only instantiate the static data members of a class without instantiating support data or member functions by using the static keyword
基本上,GCC的g++編譯器支持Borland和Cfront(AT&T)兩種模板特性。要支持Borland的模板實例化和使用特性,g++使用-frepo選項允許預處理器在處理每個翻譯單元(源代碼文件)時進行模板實例化,並把信息存在.rpo文件裏。這些文件被後面的編譯過程使用,並由鏈接器最後合併成單個編譯單元。要支持Cfront特性,g++內置了一個模板實例化庫並在鏈接的時候合併到代碼裏。Cfront要求使用模板的代碼要麼進行顯式實例化,要麼包含定義模板的申明文件。你可以把顯式實例化放在代碼的任何地方,或一個包含的頭文件裏。對於後者,你可能要去掉-fno-implicit-templates選項,這樣你只得到了顯式實例化的那些實例。
Function Name Identifiers in C++ and C
GCC編譯器預定義了2個標識符存儲當前函數的標識。__FUNCTION__標識符只存儲函數名字,__PRETTY_FUNCTION__則存儲函數的全稱。在C程序裏,這2種名字是一樣的,但是在C++程序裏它們有區別。下面的程序展示了這種區別:
#include
using namespace std;
class c {
public:
    void method_a(void)
    {
        cout<<"Function "<<__FUNCTION__<<" in "<<__FILE__<< endl;
        cout<<"Pretty Function "<<__PRETTY_FUNCTION__<<" in "
            << __FILE__ << endl;
    }
};
int main(void)
{
    c C;
    C.method_a();
    return 0;
}
運行的輸出是:
$ ./a.out
Function method_a in FUNCTION_example.cc
Pretty Function void c::method_a() in FUNCTION_example.cc
在C++裏,__FUNCTION__和__PRETTY_FUNCTION__是變量,而不是宏定義,所以#ifdef __FUNCTION__是沒有意義的。
注意
如果你的GCC是3.2版本或更高,那麼__FUNCTION__和__PRETTY_FUNCTION__的行爲就和C99定義的__func__變量是一樣的。早於3.2版本的GCC編譯器把__FUNCTION__和__PRETTY_FUNCTION__定義成字符串,所以它們可以和其他字符串進行串接操作。
Minimum and Maximum Value Operators
g++編譯器加入了?操作符,分別表示2個數值中較小的和較大的那個。比如,下面的代碼把10賦給min變量:
min = 10
而下面的代碼把15賦給max:
max = 10 >? 15;
提示
既然這些操作符是語言提供的,那麼它們也能對任何類或enum類型進行重載。
Using Java Exception Handling in C++ Applications
Java和C++的異常處理模型是不同的,雖然g++能猜測C++代碼何時使用了Java異常,你最好還是明確標識出這種情況,避免鏈接錯誤。要告訴g++一塊代碼可能使用Java異常,把下面的代碼放在該翻譯單元中任何catch和throw代碼之前:
#pragma GCC java_exceptions
你不能在一個翻譯單元裏同時使用Java和C++異常。
Visibility Attributes and Pragmas for GCC C++ Libraries
寫C++庫的時候一個普遍的問題就是可見的ELF符號太多了,其實許多符號都不能被外部使用,也不用對外公開。GCC版本4.02及更高提供了-fvisibility=value選項和相關的內置屬性,使你可以控制這種行爲,使用的方式和微軟C++編譯器提供的__declspec(dllexport)方式相似。新的-fhidden選項有2個可選值:default,導出目標文件的所有符號(這也是默認的行爲);hidden,不導出當前目標模塊的符號。還可以在函數或類前加如下代碼來進行設置:__attribute__ ((visibility("default")))和__attribute__ ((visibility("hidden")))。
默認情況下,ELF導出全部符號。要隱藏特定目標文件的符號,需要在編譯該文件的時候加上-fvisibility=hidden選項。這將導致makefile的複雜性大大增加,因爲你要麼需要手動設置每個文件的編譯選項,要麼改變全局編譯選項導致任何符號都不能導出。這在類庫正常拋出異常或者調試某些變量的時候實在是個災難。
讓指定的符號可見的好方式是聯合使用代碼屬性設置和編譯選項-fvisibility=hidden。如果要導出某個符號,先在它們的定義前加上__attribute__((visibility("default"))),比如下面這樣:
class MyClass
{
    int i;
    __attribute__ ((visibility("default"))) void MyMethod();
    …
};
然後給makefile增加-fvisibility=hidden的編譯選項,這樣所有其他的符號就被隱藏了。另一個稍微好點的方法是定義一個宏,並放到所有你不想導出的符號定義前面,然後使用默認的導出所有符號,如下所示:
#define LOCAL __attribute__ ((visibility("hidden")))
class MyClass
{
    int i;
    LOCAL void MyMethod();
    …
};
編譯時不使用-fvisibility=value選項,這樣除了MyMethod被隱藏,其他符號都被導出。
還有一種控制可見屬性的pragma語法現在還能使用,不過將來可能要去掉,如下面這樣:
extern void foo(int);
#pragma GCC visibility push(hidden)
extern void bar(int);
#pragma GCC visibility pop
符號foo會被導出,但是bar則不會。這種方式雖然很簡單方便,但是建議你還是使用visibility__attribute__
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章