題目:編織出個好程式--GCC參數篇(二)
作者:陳信宏(Ivor Chen)
E-mail add:civor@ksts.seed.net.tw or
            Ivor.bbs@bbs.ccu.edu.tw
地址:嘉義市西門街 80 號
電話:(05)2253215

    上一期所介紹的參數大體上和整個編譯過程
關,如整體參數、方言參數和除錯參數等,本期
將要更進一層的介紹許多控制細部動作的參數。

最佳化參數
==========
    最佳化一個程式的演算法有很多種:從合理
的調整程式結構流程到適當的安排變數型態等不
一而足,且除了程式方面的最佳化之外,還可配
合主機硬體優勢和OS過人之處來加以運用,但不
管如何,最佳化的目標仍是唯一的--更快、更小
、以及能配合所在環境的更好。

-O
-O1
初步最佳化,作最佳化的編譯當然會花費更多的
時間和記憶體;未使用本參數前 GCC編譯的原則
是減少編譯成本,而且各個階段的編連是各自獨
立的--可以在任一階段停下來,重新指定變數內
容,且各階段的結果和一般的狀況相同,完全符
合正常的程序,然而本參數會以整體四個階段一
起考慮,且本參數包含許多其它參數,目的就是
要減少程式大小及執行時間;使用本參數同時會
將-fthread-jumps和-fdelayed-branch開啟。

-O2
更進一步最佳化,本參數可直接使用不需配合上
一個參數,在本參數下除了「大小--速度」需犧
牲一方的演算法外其於都會用來作最佳化處理,
也就是除了 frame pointer elimination和loop
unrolling 外其餘將會利用上;和上一個參數比
起來本參數在編連時間和執行碼效能都更優越。

-O0
不作最佳化;然而若是之前有指定其它 LEVEL的
參數,將不會受到本參數的影響。

    接下來是許多和 CPU構造有關的參數,它們
的型態為-fFLAG,大部份有相反詞,而相反詞為
FLAG之前加上 no-所形成。

-ffloat-store
"不要"將浮點變數儲存在暫存器中,而且抑制其
它會改變從暫存器或記憶體所得浮點數之值的參
數功能;本參數避免了一個現象,即浮點數的值
無緣無故的加了好幾位小數出來,本來若將變數
內容放在暫存器中,會比在記憶體中快上許多,
這也是一般最佳化的方法之一,然而對許多主機
例如 68000來說其浮點暫存器所能容納的長度比
double還長,這對一般程式來說無可厚非,然而
對數值計算程式或是利IEEE浮點數的程式而言卻
是"窩裡反"的行為,本參數就是避免這種情況發
生。

-fno-default-inline
inline這個關鍵字的作用是在呼叫此類函數的地
方有著這個函數的程式碼存在,如此雖然程式變
大了但執行速度也加快許多,當使用-O時物件中
定義在 class區域中的每一個成員函數都是以
inline的型態處理,而本參數便是限定不要將這
些成員函數視為inline。

-fno-defer-pop
函數呼叫時其間傳入值和傳回值的傳遞是靠堆疊
來完成的,一般情況下編譯程式會在堆疊之中累
積一些argument後再一起 pop出去,這是為了速
率著想,而本參數的作用便是不要作堆積,進一
個便彈一個出去。

-fforce-mem
強制 memory operands在作運算前先copy到暫存
器中以加快運算速度。

-fforce-addr
強制 memory address 之內容在作運算前先copy
到暫存器中以加快運算速度。本參數對於程式整
體效率的提升比 -fforce-mem還好,但這兩個參
數並不適合任一機種。

-fomit-frame-pointer
當函數用不到 frame pointer時將它從暫存器中
清除,本參數同時抑制了任何儲存、設定或回存
frame pointer 的動作,這將使得更多的函數可
利用暫存器以提升執行效率。

-fno-inline
忽略關鍵字inline的作用。

-finline-functions
整合所有「簡單」的函數到呼叫它們的函數內,
而「簡單」的標準由 GCC自行判斷;假如一個函
數被整合到其它函數中,且其型態為static,此
函數當然不會有自己本身的輸出。

-fkeep-inline-functions
本參數要和上一參數對照;縱然一static函數被
整合到其它函數內,仍輸出執行時期可供呼叫的
version。

-fno-function-cse
不要將函數所在位址放在暫存器中,這將使得每
個呼叫函數的指令包含了函數位址,這會產生較
沒效率的程式碼,但對於某些會改變 assembler
輸出的 hacks來說,最佳化的過程會擾亂程式的
行為,那時便需使用本參數。

-ffast-math
本參數允許 GCC違反某些ANSI或IEEE規則來作運
算,這將使得執行速度加快,但對於運算程式等
需依賴數值精密度作計算者則本參數不應和任何
-O參數共同使用,因為很有可能產生錯誤結果。

前置處理器參數
==============
    直觀來說前置處理器的作用為處理程式之中
以 #開頭的命令,這些命令於常見的有標頭檔的
引入和定義常數及巨集等,可以說是在編譯之前
的準備動作,有時也可用依某常數定義與否來決
定那些程式片段是否要編譯,這對移植的工作有
很大的助益,例如同樣要作到印出檔案名稱,在
不同的OS和檔案格式上所得的結果就有蠻大的差
異,在此就可分別寫不同的處理碼,然後再利用
#ifdef等命令適機編連,不僅節省程式碼,還能
加快速度;以下的參數,便是和前置處理時期有
關者,而且許多參數需和-E配合使用。

-include FILE
在編譯目標程式之前先編譯FILE,而且FILE並無
只能為標頭檔的限定,若是和-D及-U合併使用時
以本參數為優先處理,當參數列中不只一個本參
數及-imacros時依其順序執行。

-imacros FILE
本參數和上一個參數幾乎相同,同樣都是在編譯
目標程式之前先編譯FILE,所不同的是本參數會
捨棄FILE的輸出,這使得FILE中唯有放置巨集定
義才有作用。

-idirafter DIR
將 DIR內的路徑加至第二包含路徑中,當在主要
包含路徑中找不到標頭檔時便可到第二來尋找,
而主要包含路徑便是由-I所指定。

-iprefix PREFIX
設定PREFIX之內容為-iwithprefix中之prefix預
設值。

-iwithprefix DIR
增加一個路徑到第二包含路徑中去,而這個路徑
由 DIR和-iprefix中的PREFIX之內容聯合組成。

-iwithprefixbefore DIR
增加一個路徑到主要包含路徑中去,而這個路徑
由 DIR和-iprefix中的PREFIX之內容聯合組成。

-nostdinc
不要在標準系統目錄下尋找標頭檔,只在目前目
錄和-I所指定的目錄下尋找,因此這對於自行建
立程式庫有幫助,有關目錄的所有選項請看稍後
的「搜尋參數」。

-undef
不要預先定義任何非標準的巨集。

-E
本參數在「整體參數」有提到過,作用為令 GCC
完成前置處理後便停止。

-C
命令前置處理器不要捨棄註解部份,本參數需和
-E共用。

-P
命令前置處理器不要產生 #line命令,本參數需
和-E共用。

-M
命令前置處理器輸出給make程式用的規則,內容
為每個目的檔的依存規則,對於每個source,前
置處理器會將其target定為目的檔(object),而
其中所使用到的標頭檔便成為依存檔,有關本參
數的最佳參考為 ~/linux下Makefile中depend這
項依存規則的內容;另外一個如本參數功能的方
法為設定環境變數"DEPENDENICS_OUTPUT"。

-MM
本參數唯一和-M不同之處在於輸出的規則中唯有
使用者自定標頭檔(#include "FILE" 部份)才會
列入,而系統標頭檔(#include  部份)並
不會列入。

-MD
作用如同-M一樣,然而在本參數會將規則輸出到
和.o檔同檔名的.d檔中(-M要用重新導向的方法)
,而Mach utility中的"md"可將各別的.d檔集合
成可供make使用的依存檔。

-MMD
為 -MM和 -MD功能的交集。

-H
印出所使用的標頭檔。

-DMARCO
定義巨集 MARCO為字串"1"。

-DMARCO=DEFN
定義巨集 MARCO為DEFN;此時所有的-D參數將比
-U參數優先執行。

-UMARCO
取消 MARCO的定義;此時-U還是比-D後執行,但
卻在-include和-imarcos之前。

-dM
命令前置處理器在前置處理完畢後列出所有有效
的巨集名稱,本參數需和-E共用。

-dD
命令前置處理器輸出所有的巨集定義內容。

-dN
命令前置處理器輸出所有內定之外的巨集定義。

-trigraphs
支援ANSI C trigraphs,本參數包含於 -ansi。

傳遞參數至組譯程式(Assembler)
=============================

-Wa,OPTION
將OPTION的內容傳給組譯程式,其內容是由參數
們所組成,不同的參數間以逗號( ,)隔開。

連結參數
========
    當原始程式編譯或組譯完成候,便完成了基
本架構,如條件判斷或是迴圈等已正確的轉換成
機器語言型式,這便是.obj的內容,在不同的主
機和OS上.obj的格式也不相同,在程式中通常都
會呼叫一些函數,這些函數的可執行碼便放在程
式庫中,需要把它們加進來才算是完整的可執行
檔,而這個過程便稱為連結(link),本區所要介
紹的為控制連結過程的各項參數。

-OBJECT-FILE-NAME
連結之時會以.o檔為模版造出最終可執行檔,這
個過程是以.o的字尾來判定是否為Object檔,若
是當Object檔字尾不是.o時,便可使用本參數,
而OBJECT-FILE-NAME的內容是為非標準Object檔
的檔名。

-lLIBRARY
連結時尋找 LIBRARY.a程式庫作為程式庫來源,
本參數所指的程式庫為附加方式,即標準程式庫
仍會用來作連結,不管本參數有設定,而相關的
目錄路徑則為標準路徑加上-L所指定的路徑;如
果參數列中有兩個以上的目的檔,本參數的位置
有很大的影響,如在 "foo.o -lz bar.o"中 -lz
只對 foo.o有效,即 bar.z無法連結到 z.a中的
特定函數。

-lobjc
若是編譯 Objective C程式則需以本參數來連結
專用程式庫。

-nostartfiles
編譯完成的可執行檔在啟動時都會經由標準系統
啟動程式(standard system startup file)作啟
動的動作,這個檔案依OS的不同而有不同的功能
,本參數便是命令連結程式不要將啟動程式連入
執行檔中。

-nostdlib
當連結時不要使用標準程式庫和啟動程式,但由
-l指定者不在此限。

-static
對於提供動態連結(dynamic linking) 的系統,
本參數抑制與共享程式庫作連結(也就是說GCC的
內定為動態連結)。

-Xlinker OPTION
在 GCC中內定的連結程式為ld,而且 GCC能以內
部的方式將參數傳給ld,然而在某些系統下能在
Makefile中改變LD變數的內容來指定特別的連結
程式,此時控制參數便需本參數來作傳遞,此時
GCC 會將OPTION的內容傳給特定連結程式,如果
傳遞的參數有附加選項,則需本參數兩個才能成
功,如欲傳遞 '-assert definitions'時,則需
寫成 -Xlinker -assert -Xlinker definitions
才行。

-W1,OPTION
對於 GCC能辨認的連結程式便可用本參數來傳遞
控制值,在OPTION中若有一個以上的參數,其間
使用逗號( ,)隔開。

-u SYMBOL
假裝符號SYMBOL未曾定義,使得程式庫模組可定
義同一符號。

搜尋參數
========
    本系列參數定義有關標頭檔、程式庫和部份
編譯程式的搜尋路徑。

-IDIR
增添 DIR中的路徑到標頭檔搜尋路徑之中。

-I-
任何在本參數之前的-I參數,其所設立的路徑將
只會用在對 #include "FILE"的搜尋,對於搜尋
#include  並無影響,然而在本參數之後
的-I,其路徑對所有的#include皆有作用,因此
本參數還會使得目前目錄不是第一個搜尋"FILE"
的路徑;由於本參數並不會影響對標準系統目錄
的標頭檔搜尋,因此和 -nostdinc各自獨立。

-LDIR
增添 DIR到由-l所指定程式庫的搜尋路徑去。

-BPREFIX
本參數主要作用為指定如何去尋找和編譯程式有
關的可執行檔、程式庫及資料檔等;編譯時其實
是由四個子程式所共同完成--cpp、cc1、as和ld
,這四個程式在 gcc的統籌驅動下完成編連的四
個步驟,在沒指定本參數的情況下, gcc會先在
/usr/lib/gcc/ 和 /usr/local/lib/gcc-lib/下
尋找相關檔案,若未找到再依PATH所指去尋找,
若是使用本參數,則PREFIX中所指路徑將是第一
個尋找的目標。

警告參數
========
    警告是在編譯的過程中所發出的建議訊息,
警告並非是錯誤,編譯後的程式仍可執行,不過
警告的目的為指出某段可能會出錯的宣告方式語
法,因此不可因不影響程式執行而掉以輕心;警
告參數的目的為對某種語法提出(或抑制)警告訊
息,以方便除錯或是順利執行,通常是以-W為開
頭,這些參數的相反詞為加 no-所形成,而不管
是那種類型,都不是 GCC的內定值。

-fsyntax-only
只檢查程式之語法有無錯誤而不作編譯。

-w
抑制所有警告訊息。

-Wno-import
抑制所有和使用 #import有關之警告訊息。

-pedantic
依嚴格的ANSI C的標準發出警告訊息,並駁回任
使用不允許擴充函數的程式。

-pedantic-errors
作用如同 -pedantic,但除了警告外還會產生錯
誤訊息。

-W
對下列情況提出警告:
○一個 nonvolatile自動變數將被改成 longjmp
  時,這種情況唯有作最佳化時才會發生。
○當一個函數可能有傳回值,也可能不傳回任何
  值時,例子如下:
    foo (a)
    { if (a > 0) return a; }
○沒任何作用的 expression敘述。
○一個unsigned數值和零比較大小時。
○如 'x<=y<=z'的表示式,此表示式 GCC會將其
  處理成 '(x<=y?1:0)<=z'以便程式能順利執行
  ,當然這樣和原來想表現的意思差蠻多的。
○當storage-class specifiers如'static'並不
  是所有宣告中最先執行之時。

-Wimplicit
當某函數或某參數implicitly宣告時提出警告。

-Wreturn-type
當一函數內定傳回值為 int時卻作別種型態的宣
告便提出警告,或是當一函數無傳回值(void),
然而其中的return接一傳回值時便發出對return
的警告。

-Wunused
對某一local變數自宣告後便沒再使用、declare
一static函數後但未先define、還有一段敘述算
出一個結果,這個結果接下來卻未再使用等以上
三種情況提出警告。

-Wswitch
使用一列舉(enum)形態的變數為switch的 index
時,接下來卻缺少一個或以上的case作配合,或
者是case的 label超出了enum中的選項時,便發
出警告訊息,若是沒有必要列出每個case時可用
default 代替。

-Wcomment
當'/*'符號"又"出現在註解中時。

-Wformat
檢查printf、scanf...等函數其中第二個以後的
參數型態是否和第一個字串中所寫有符合。

-Wchar-subscripts
當陣列的註標(subscript) 之型態為char時便發
出警告,因為在某些主機上char為signed。

-Wuninitialized
當一自動變數未給予初值便使用時;如下的例子
  {
    int x;
    switch (y)
      {
      case 1: x = 1;
        break;
      case 2: x = 4;
        break;
      case 3: x = 5;
      }
    foo (x);
  }
這個例子看起來沒錯,因為只要 y為 1、 2或 3
則 x都會有初值,但 GCC對於這樣的寫法並不夠
聰明到能看出 x會有初值這件事,因此對foo(x)
而言其中的 x就出問題了,然而當未使用最佳化
編譯時並不會有本警告出現。

-Wparentheses
當 parentheses在以下這些敘述中省略時:當預
期將為true value卻有assignment時,或是有令
人頭昏眼花的nest operators時。

-Wenum-clash
當兩個不同的enum型態作轉換時。(C++ only)

-Wtemplate-debugging
若是在程式中使用了模版(template)但除錯功能
未完全支援時。(C++ only)

-Wall
本參數為以上所有-W參數的總集,且本參數為最
推薦使用者,因為以上的情況便包含了一般情況
下該警告的事項。

    接下來的這些參數並不包含於 -Wall之中,
這些參數所要警告的為一些結構上的問題。

-Wtraditional
對傳統和ANSI C的一些結構上的差異提出警告:
○switch的 operand型態為long。
○函數在 block中宣告為外部(external)而後在
  block 結束後使用。
○巨集的參數是位於巨集本體的字串內容中,這
  種情況對傳統 C是允許的,但對ANSI C會有不
  相容發生。

-Wshadow
當一區域變數shadow另一區域變數時。

-Wid-clash-LEN
當兩個以上的辨別字(identifier)在開頭和 LEN
相同時。

-Wpointer-arith
當使用 size of在函數時。

-Werror
將所有警告轉換成錯誤,這會讓編連停止。

後言
====
    對於經常使用 C/C++的使用者來說,不管是
用來寫商業程式或是交學校的作業,GCC 是一套
很不錯的選擇,由介紹了兩期仍無法完全含蓋的
參數部份,就可知它的能力強撼,然而它缺少一
個友善的環境(如TC或是MSC中的IDE),不免令人
卻步,不過利用make和Makefile的統一管理,一
切就變得清楚許多;下次若有機會,再來討論一
些安裝 GCC的方式和其它相關設定。


修改日期: