用gcc 自製 Library

Library可分成三種,static、shared與dynamically loaded。

1. Static libraries

Static 程式庫用於靜態連結,簡單講是把一堆object檔用ar(archiver)
包裝集合起來,檔名以 `.a’ 結尾。優點是執行效能通常會比後兩者快,
而且因為是靜態連結,所以不易發生執行時找不到library或版本錯置而
無法執行的問題。缺點則是檔案較大,維護度較低;例如library如果發
現bug需要更新,那麼就必須重新連結執行檔。

1.1 編譯

編譯方式很簡單,先例用 `-c’ 編出 object 檔,再用 ar 包起來即可。

____ hello.c ____
#include
void hello(){ printf(“Hello “); }

____ world.c ____
#include
void world(){ printf(“world.”); }

____ mylib.h ____
void hello();
void world();

$ gcc -c hello.c world.c /* 編出 hello.o 與 world.o */
$ ar rcs libmylib.a hello.o world.o /* 包成 limylib.a */

這樣就可以建出一個檔名為 libmylib.a 的檔。輸出的檔名其實沒有硬性規定,
但如果想要配合 gcc 的 ‘-l’ 參數來連結,一定要以 `lib’ 開頭,中間是你要
的library名稱,然後緊接著 `.a’ 結尾。

1.2 使用

____ main.c ____
#include “mylib.h”
int main() {
hello();
world();
}

使用上就像與一般的 object 檔連結沒有差別。

$ gcc main.c libmylib.a

也可以配合 gcc 的 `-l’ 參數使用

$ gcc main.c -L. -lmylib

`-Ldir’ 參數用來指定要搜尋程式庫的目錄,`.’ 表示搜尋現在所在的目錄。
通常預設會搜 /usr/lib 或 /lib 等目錄。
`-llibrary’ 參數用來指定要連結的程式庫 ,’mylib’ 表示要與mylib進行連結
,他會搜尋library名稱前加`lib’後接`.a’的檔案來連結。

$ ./a.out
Hello world.

2. Shared libraries

Shared library 會在程式執行起始時才被自動載入。因為程式庫與執行檔
是分離的,所以維護彈性較好。有兩點要注意,shared library是在程式起始
時就要被載入,而不是執行中用到才載入,而且在連結階段需要有該程式庫
才能進行連結。

首先有一些名詞要弄懂,soname、real name與linker name。

soname 用來表示是一個特定 library 的名稱,像是 libmylib.so.1 。
前面以 `lib’ 開頭,接著是該 library 的名稱,然後是 `.so’ ,接著
是版號,用來表名他的介面;如果介面改變時,就會增加版號來維護相容度。

real name 是實際放有library程式的檔案名稱,後面會再加上 minor 版號與
release 版號,像是 libmylib.so.1.0.0 。

一般來說,版號的改變規則是(印象中在 APress-Difinitive Guide to GCC中有
提到,但目前手邊沒這本書),最尾碼的release版號用於程式內容的修正,
介面完全沒有改變。中間的minor用於有新增加介面,但相舊介面沒改變,所以
與舊版本相容。最前面的version版號用於原介面有移除或改變,與舊版不相容
時。

linker name是用於連結時的名稱,是不含版號的 soname ,如: libmylib.so。
通常 linker name與 real name是用 ln 指到對應的 real name ,用來提供
彈性與維護性。

2.1 編譯
shared library的製作過程較複雜。

$ gcc -c -fPIC hello.c world.c

編譯時要加上 -fPIC 用來產生 position-independent code。也可以用 -fpic
參數。 (不太清楚差異,只知道 -fPIC 較通用於不同平台,但產生的code較大
,而且編譯速度較慢)。

$ gcc -shared -Wl,-soname,libmylib.so.1 -o libmylib.so.1.0.0
hello.o world.o

-shared 表示要編譯成 shared library
-Wl 用於參遞參數給linker,因此-soname與libmylib.so.1會被傳給linker處理。
-soname用來指名 soname 為 limylib.so.1
library會被輸出成libmylib.so.1.0.0 (也就是real name)

若不指定 soname 的話,在編譯結連後的執行檔會以連時的library檔名為
soname,並載入他。否則是載入soname指定的library檔案。

可以利用 objdump 來看 library 的 soname。

$ objdump -p libmylib.so | grep SONAME
SONAME libmylib.so.1

若不指名-soname參數的話,則library不會有這個欄位資料。

在編譯後再用 ln 來建立 soname 與 linker name 兩個檔案。
$ ln -s libmylib.so.1.0.0 libmylib.so
$ ln -s libmylib.so.1.0.0 libmylib.so.1

2.2 使用

與使用 static library 同。

$ gcc main.c libmylib.so

以上直接指定與 libmylib.so 連結。

或用

$ gcc main.c -L. -lmylib

linker會搜尋 libmylib.so 來進行連結。

如果目錄下同時有static與shared library的話,會以shared為主。
使用 -static 參數可以避免使用shared連結。

$ gcc main.c -static -L. -lmylib

此時可以用 ldd 看編譯出的執行檔與shared程式庫的相依性
$ldd a.out
linux-gate.so.1 => (0xffffe000)
libmylib.so.1 => not found
libc.so.6 => /lib/libc.so.6 (0xb7dd6000)
/lib/ld-linux.so.2 (0xb7f07000)
輸出結果顯示出該執行檔需要 libmylib.so.1 這個shared library。
會顯示 not found 因為沒指定該library所在的目錄,所找不到該library。

因為編譯時有指定-soname參數為 libmylib.so.1 的關係,所以該執行檔會
載入libmylib.so.1。否則以libmylib.so連結,執行檔則會變成要求載入
libmylib.so

$ ./a.out
./a.out: error while loading shared libraries: libmylib.so.1:
cannot open shared object file: No such file or directory

因為找不到 libmylib.so.1 所以無法執行程式。
有幾個方式可以處理。

a. 把 libmylib.so.1 安裝到系統的library目錄,如/usr/lib下
b. 設定 /etc/ld.so.conf ,加入一個新的library搜尋目錄,並執行ldconfig
更新快取
c. 設定 LD_LIBRARY_PATH 環境變數來搜尋library
這個例子是加入目前的目錄來搜尋要載作的library
$ LD_LIBRARY_PATH=. ./a.out
Hello world.

3. Dynamically loaded libraries

Dynamicaaly loaded libraries 才是像 windows 所用的 DLL ,在使用到
時才載入,編譯連結時不需要相關的library。動態載入庫常被用於像plug-ins
的應用。

3.1 使用方式
動態載入是透過一套 dl function來處理。
#include void *dlopen(const char *filename, int flag);
開啟載入 filename 指定的 library。
void *dlsym(void *handle, const char *symbol);
取得 symbol 指定的symbol name在library被載入的記憶體位址。
int dlclose(void *handle);
關閉dlopen開啟的handle。
char *dlerror(void);
傳回最近所發生的錯誤訊息。

____ dltest.c ____
#include
#include
#include int main() {
void *handle;
void (*f)();
char *error;

/* 開啟之前所撰寫的 libmylib.so 程式庫 */
handle = dlopen(“./libmylib.so”, RTLD_LAZY);
if( !handle ) {
fputs( dlerror(), stderr);
exit(1);
}

/* 取得 hello function 的 address */
f = dlsym(handle, “hello”);
if(( error=dlerror())!=NULL) {
fputs(error, stderr);
exit(1);
}
/* 呼叫該 function */
f();
dlclose(handle);
}

編譯時要加上 -ldl 參數來與 dl library 連結
$ gcc dltest.c -ldl
結果會印出 Hello 字串
$ ./a.out
Hello

關於dl的詳細內容請參閱 man dlopen


參考資料:

Creating a shared and static library with the gnu compiler [gcc]
http://www.adp-gmbh.ch/cpp/gcc/create_lib.html

Program Library HOWTO
http://tldp.org/HOWTO/Program-Library-HOWTO/index.html

APress – Definitive Guide to GCC

BKDRHash

// BKDR Hash Function
unsigned int BKDRHash(char *str)
{
unsigned int seed = 131; // 31 131 1313 13131 131313 etc..
unsigned int hash = 0;

while (*str)
{
hash = hash * seed + (*str++);
}

return (hash & 0x7FFFFFFF);
}

里斯特資訊媒體股份有限公司

里斯特資訊媒體股份有限公司

公司簡介

里斯特資訊媒體(Richi)成立於2007年7月,以數位內容加值服務為核心,不斷嘗試創新網路服務模式,期望在個人化的網路時代裡,為產業尋找新形態的服務架構,為消費者提供更符合個人化需求的服務支援。

成立3年,Richi屢獲國內外創新商業模式獎項。2010年在號稱「矽谷創投聖經」、足以影響全球科技業趨勢的雜誌「紅鯡魚」(Red Herring)年度新創公司評選中,以「虛擬貨幣交換中心」機制的概念,擊敗上百家公司,與台灣興櫃股王PChome商店街並列「2010亞州科技 100強」;並在2011年1月底在美國洛杉磯獲頒「2010 全球科技100強」,是台灣唯一獲得此殊榮的公司,也是台灣暌違已久,再度拿下這個全球新創團隊聖盃的唯一團隊。國內知名雜誌「商業週刊」專訪Richi 創業團隊,將Richi喻為未來的網路央行,並認為Richi是目前台灣最具發展潛力的新創網路公司之一。

看準社群行銷、行動裝置、小額付費與個人化內容等趨勢將帶來的龐大商機,Richi 未來將持續專注於發展社群貨幣(Social Payments)機制,並在台灣網路產業起飛的歷史中,成為所有網路創新團隊與創作者的最佳技術支援後盾。

你也想要在這個產業轉型的歷史性時刻中,占有一席之地嗎?Richi正在尋找一起實現夢想的夥伴。

透過嚴謹的軟體專案管理流程訓練,Richi團隊裡的各個不同部門,不管是業務、行銷、設計或是研發,所有成員都能共同參與創新網路服務的發想、討論與實現,與不同專業的夥伴分工合作,共同實現網路世界裡無限可能的創意。

Richi認為,在網路的時代裡,「人」才是最重要的價值。如果你對於網路的未來充滿無限想像,如果你也認為堅持和努力是實現夢想的最重要特質,我們非常期待你的加入。

主要商品及服務

– 線上虛擬點數交換平台金流機制
– 社群貨幣(Social Payments)與贊助機制
– 內容社群。包含數位時代OpenCrunch、財訊Wealth、河岸留言Open Jam
– 協辦Tech Orange新媒體。以台灣的TechCrunch為目標,創立三周即進入台灣前千大網站。
– 數位內容與數位廣告服務
– Skype VOIP加值服務
– 移動設備之通訊軟體與加值服務

經營理念

Richi在更開放與自由的資訊環境中,以數位內容加值服務為核心,不斷嘗試創新網路服務模式,期望在個人化的網路時代裡,為產業尋找新形態的服務架構,為消費者提供更符合個人化需求的服務支援。並以台灣為出發點,將服務輻射式推廣至不同語系的國家與地區。

php curl multi interface

// create the multi curl handle

$mh = curl_multi_init();

$handles = array();

for($i=0;$i<5;$i++)

{

// create a new single curl handle

$ch = curl_init();

// setting several options like url, timeout, returntransfer

// simulate multithreading by calling the wait.php script and sleeping for $rand seconds

curl_setopt($ch, CURLOPT_URL, "http://put your url here/wait.php?seconds=".($i+1));

curl_setopt($ch, CURLOPT_HEADER, 0);

curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

curl_setopt($ch, CURLOPT_TIMEOUT, 30);

// add this handle to the multi handle

curl_multi_add_handle($mh,$ch);

// put the handles in an array to loop this later on

$handles[] = $ch;

}

// execute the multi handle

$running=null;

do

{

curl_multi_exec($mh,$running);

// added a usleep for 0.25 seconds to reduce load

usleep (250000);

} while (
$running > 0);

// get the content of the urls (if there is any)

for($i=0;$i<count($handles);$i++)

{

// get the content of the handle

$output.= curl_multi_getcontent($handles[$i]);

// remove the handle from the multi handle

curl_multi_remove_handle($mh,$handles[$i]);

}

// echo the output to the screen

echo $output;

// close the multi curl handle to free system resources

curl_multi_close($mh);

?>

修正htmlcxx中的几处bug

1.Node::parseAttributes 在解析这种 标签时会有问题!
原因:
stat.js?后面两个字节是a1 a7,(还有中文问题)显然不是ASCII表中的。
而Node.cc(Line89) 有while (*end && !isspace(*end) && *end != ‘>’) end++;
其中isspace(*end)中会有如下断言:_ASSERTE((unsigned)(c + 1) <= 256); //要求调用者保证传入的参数必须属于ASCII码
修改如下:
//while (*end && !isspace(*end) && *end != ‘>’) end++;
while (*end &&((unsigned)*end > 255 || !isspace(*end) ) && *end != ‘>’) end++;

3.ParserSax.tcc也存在如上相同问题:
修改如下
template
_Iterator
htmlcxx::HTML::ParserSax::skipHtmlComment(_Iterator c, _Iterator end)
{
while ( c != end ) {
if (*c++ == ‘-‘ && c != end && *c == ‘-‘)
{
_Iterator d(c);
while (++c != end &&((unsigned)*c > 255 || !isspace(*c) ) && *c != ‘>’);
if (c == end || *c++ == ‘>’) break;
c = d;
}
}

return c;
}

FreeBSD 個人化 .cshrc 部份設定

#chsh -s /bin/tcsh <=切換到tcsh

在用vipw修改自己帳號的預設shell改成tcsh (通常在帳號那欄的最後面!)

在freebsd下的tcsh會先去讀/etc/csh.cshrc/etc/csh.login的設定再去讀取

帳號下的.cshrc.login 我們可以經由這幾個檔案讓我們一登入時候生效。

clover自己是先在/etc/csh.cshrc內加入下面幾行程式碼:

setenv EDITOR vi <= 預設文書編輯器為vi
alias ls ls -i <= 讓 ls 指令 預設成ls -i
alias mv mv -i <=讓mv指令 預設成 mv -i
alias cp cp -i <= 讓cp 指令預設成 cp -i
alias h history 25 <=讓歷史可以存25個指令

然後 按esc 存檔離開, 再去個人帳號目錄下去修改.cshrc 加入

set prompt=”%B# %m,%n [%/] #”

其中%B 是粗體字%m是hostname%n是帳號名[%/]是目前路徑
最後打上:

#source ~/.cshrc

GDB

出處: http://www.cmlab.csie.ntu.edu.tw/~daniel/linux/gdb.html

GNU debugger

常用參數

gdb [options] [executable-file [core-file or process-id]]
gdb [options] --args executable-file [inferior-arguments ...]

GNU manual

Lightweight Text UI

  • gdbtui — gdb 自已提供的 curses mode 介面前端
  • cgdb — 提供 vim like 的程式碼檢視功能

基本操作

執行環境

    程式的參數 (Program’s Arguments)
    set args — 設定程式的參數
    show args — 顯示 set args 所設定的程式參數

    工作目錄 (Program’s Working Directory)
    cd — 改變工作目錄,和在 shell 下使用 cd 相同
    pwd — 顯示工作目錄

    環境變數 (Environment Variables)
    show environment — 顯示所有環境變數的內容
    show environment varname — 顯示所指定環境變數的內容
    set environment varname [=value] — 設定環境變數
    unset environment varname — 取消環境變數設定
    path DIR — 將 DIR 加到 PATH 中
    path — 顯示 PATH 的內容

    SHELL
    shell command –呼叫 shell 執行外部命令。e.g shell ls

程式執行

  • run (r) — 開始執行程式
  • continue (c) — 離開中斷點,繼續執行程式
  • next (n) — 單步執行 (step over)
  • step (s) — 進入函式 (step into)
  • until (u) — 離開 while, for等迴圈。 執行到程式碼的行數比目前的大,如果目前是迴圈的最後一行,就會離開迴圈
  • finish — 繼續執行程式直到函式返回
  • start –在 main 設置暫時中斷點,並開始執行程式
  • advance — 執行程式直到指定的位置
  • run arglist — 同 run,並指定程式參數
  • start arglist — 同 start,並指定程式參數
  • kill (k) –終止程式執行
  • quit (q) –離開 GDB

觀看程式碼

  • list (l) –列出目前執行程式碼前後各五行的程式碼;或是接著前次 list 的程式碼,列出之後的程式碼
  • “list -” — 上次列出程式碼的前十行,類似向上翻頁
  • list *ADDRESS — 列出包含指定位址的程式碼,常與 bt 配合使用
  • (gdb) bt
    #0 0x08048405 in memory_violation () at test.cpp:9
    #1 0x08048427 in main () at test.cpp:15
    (gdb) list *0x08048405
  • directory DIR — 新增一個路徑到程式碼搜尋路徑
  • show directories –顯示目前的程式碼搜尋路徑
  • disassemble (disas) –反組譯目前執行的程式碼
  • disassemble start_addr end_addr –反組譯指定範圍的程式碼。e.g. disas 0x32c4 0x32e4

設定中斷點 (breakpoint)

  • break 簡寫指令 b
  • break (b) line_number — 在指定的行數設定中斷點
  • break function — 在指定的 function 設定中斷點。e.g. break main
  • break filename:line_num — 在指定檔案的指定行數設定中斷點。e.g. break main.c:10
  • break [LOCATION] [if CONDITION] — 條件式中斷點,當 CONDITION 滿足時才中斷。e.g. break main.c:10 if var > 10
  • info break — 列出所有的中斷點及編號
  • delete number — 刪除指定編號的中斷點
  • disable number — 使指定編號的中斷點失效
  • enable number — 取消 disable,使指定編號的中斷點生效

設定 watchpoint

  • watch varname — 設定 watch point 監看變數,當變數被寫入時,中斷程式
  • rwatch varname — 設定 watch point 監看變數,當變數被讀取時,中斷程式
  • awatch varname — 設定 watch point 監看變數,當變數被讀取或寫入時,中斷程式
  • watch *(int *)0x12345678 — 設定 watch point 監看記憶體位址,監看範圍由變數型別決定,當此記憶體位址被寫入時,中斷程式
  • info watch — 列出所有的 watchpoint

觀察變數資料

  • print (p) varname — 顯示變數內容
  • printf expression –使用 C 語言的格式化字串 (printf format string) 功能來顯示。e.g. printf “var=%dn”, var
  • display varname— 遇到中斷點時,自動顯示變數的內容
  • undisplay display_num — 取消指定編號的自動顯示
  • info display — 列出所有 display 及編號
  • whatis varname — 顯示變數型別
  • ptype varname –顯示 class, struct 的定義內容
  • info locals — 顯示目前的區域變數

觀察記憶體

  • x/8xw ADDRESS — 印出 8 個 word (4 bytes) 的記憶體內容
  • x/FMT ADDRESS — 詳細的 FMT 說明,請參考 help x
  • dump memory FILE START END — 將指定範圍內的記憶體內容 dump 到檔案

顯示函式呼叫堆疊 (Call Stack)

GDB 以函式為單位分成一個個的 frame,每一個 frame 各代表一個函式。
如 main() 是一個 frame,main() 裡面所呼叫的函式是另外一個 frame。
當呼叫函式時,會把目前的 frame 推到堆疊。這個堆疊就是 frame stack,也就是一般所認知的 call stack。

  • backtrace (bt) –顯示目前函式呼叫堆疊 (Call Stack) 內容
  • backtrace full — 一併列出區域變數 (local variable)
  • where –顯示目前程式的執行位置,與 backtrace 的作用相同
  • frame frame_num — 跳到指定編號的 frame
  • up — 往上一個 frame
  • down — 往下一個 frame
  • info args — 顯示傳給函式的呼叫參數

Multi-thread

  • info threads — 顯示目前所有的 thread
  • thread thread_num — 切換 GDB 到指定的 thread_num

改變程式行為

  • return — 直接從函式目前執行位置返回,放棄未執行的部份
  • return expression — 執行 return, 並傳回 expression 的值
  • set varname=xxx — 更改變數值

顯示程式相關資訊

  • info sharedlibrary — 顯示被載入的共享函式庫 (shared object library),及載入的記憶體位置
  • info registers — 顯示基本暫存器的內容
  • info all-registers — 顯示所有暫存器的內容
  • print $eax — 顯示 eax 暫存器的內容

利用 core file 來幫助除錯

當程式產生 segmentation fault 時,系統會將當時的所有狀態,包括變數值,記憶體資料以及程式中各個函式的呼叫狀態 dump 成一個 core file。 我們可以用 GDB 來讀取 core file,分析當時引發 segmentation fault 的原因。

下面的程式嘗試寫入記憶體位址為 0 的區域,引發系統產生 segmentation fault

#include 

//This function will cause "Segmentation fault"
void memory_violation()
{
char* ptr = 0;
for(int i=0; i<10;>

編譯指令

$ g++ -g test.cpp -o mytest

設定 core file 最大 size
core file size 預設為 0,這裡改成沒有限制

 $ ulimit -c unlimited

執行程式

$  ./mytest
Segmentation fault (core dumped)

利用 GDB 讀取 core file

  1. 利用 bt 找出發生錯誤的地方
  2. 利用 list *ADDRESS 列出產生錯誤的程式碼
  3. 利用 print 觀察變數值
$ gdb ./mytest core
...
(gdb) bt
#0 0x08048405 in memory_violation () at test.cpp:9
#1 0x08048427 in main () at test.cpp:15
(gdb) list *0x08048405
0x8048405 is in memory_violation() (test.cpp:9).
4 void memory_violation()
5 {
6 char* ptr = 0;
7 for(int i=0; i<10;>print ptr
$1 = 0x0

Last Modified: 03/14/2008 15:58:11

GCC编译优化指南

前言

  网上关于编译优化的文章很多,但大多零零散散,不成体系,本文试图给出一个完整和清晰的优化思路,同时 提供在实践中如何进行优化的详尽参考。但是,在介绍所有优化知识之前首先引用LFS-Book中的一句忠告:“使用编译器优化得到的小幅度性能提升,与它 带来的风险相比微不足道”。你还要进行优化吗?

  %@&#=^%~*# …

  OK, crazy guy! Let’s Go!!

   在继续之前,作者还是奉劝各位:如果追求极致的优化,那么它将是一件既耗时又麻烦的事情,你会陷入无止尽的测试、测试、再测试……另外 Gentoo wiki 上有这么一句话:”GCC has well over a hundred individual optimization flags and it would be insane to try and describe them all.”所以本文不会涉及全部GCC优化选项。最后作者还是再罗唆一句:优化应当适可而止为好,将精力留出来做一些其它事情会更有意义!

  先决条件

   本文的主要读者是 LFS/Gentoo 的玩家,基本上比较 crazy 的玩家都接触过,如果你之前从未使用过 LFS/Gentoo ,请先按照《Linux From Scratch 6.2 中文版》做一遍 LFS ,然后再来阅读此文将会更有意义。另外,本文是建立在《深入理解软件包的配置、编译与安装》一文基础之上的,在开始阅读本文之前,请先阅读它。

  基本原理

  我们首先从三个方面来看与优化相关的内容:

  从运行时的依赖关系来看,对性能有较大影响的组件有 kernel 和 glibc ,虽然这严格说来这不属于本文的话题,但是经过精心选择、精心配置、精心编译的内核与C库将对提高系统的运行速度起着基础性的作用。

   从被编译的软件包来看,每个软件包的 configure 脚本都提供了许多配置选项,其中有许多选项是与性能息息相关的。比如,对于 Apache-2.2.6 而言,你可以使用 –enable-MODULE=static 将模块静态编译进核心,使用 –disable-MODULE 禁用不需要的模块,使用 –with-mpm=MPM 选择一个高效的多路处理模块,在不需要IPv6的情况下使用 –disable-ipv6 禁用IPv6支持,在不使用线程化的MPM时使用 –disable-threads 禁用线程支持,等等……这部分内容显然不可能在本文中进行完整的讲述,本文只能讲述与优化相关的通用选项。针对特定的软件包,请在编译前使用 configure –help 查看所有选项,并精心选择。

  从编译过程自身来看,将源代码编译为二进制文件是在 Makefile 文件的指导下,由 make 程序调用一条条编译命令完成的。而将源代码编译为二进制文件又需要经过以下四个步骤:预处理(cpp) → 编译(gcc或g++) → 汇编(as) → 连接(ld) ;括号中表示每个阶段所使用的程序,它们分别属于 GCC 和 Binutils 软件包。显然的,优化应当从编译工具自身的选择以及控制编译工具的行为入手。

  大体上编译优化就这”三板斧”(其实是”三脚猫”)了,本文接下来的内容将讨论这只猫的后两只脚。

  编译工具的选择

   对于编译工具自身的选择,在假定使用 Binutils 和 GCC 以及 Make 的前提下,没什么好说的,基本上新版本都能带来性能提升,同时比老版本对新硬件的支持更好,所以应当尽量选用新版本。不过追新也可能带来系统的不稳定,这 就要针对实际情况进行权衡了。本文以 Binutils-2.18 和 GCC-4.2.2/GCC-4.3.0 以及 Make-3.81 为例进行说明。

  configure 选项

  这里我们只讲解通用的”体系结构选项”,由于”特性选项”在每个软件包之间千差万别,所以不可能在此处进行讲解。

  这部分内容很简单,并且其含义也是不言而喻的,下面只列出常用的值:

  i586-pc-linux-gnu

  i686-pc-linux-gnu

  x86_64-pc-linux-gnu

  powerpc-unknown-linux-gnu

  powerpc64-unknown-linux-gnu

  如果你实在不知道应当使用哪一个,那么就干脆不使用这几个选项,让 config.guess 脚本自己去猜吧,反正也挺准的。

  编译选项

  让我们先看看 Makefile 规则中的编译命令通常是怎么写的。

  大多数软件包遵守如下约定俗成的规范:

  #1,首先从源代码生成目标文件(预处理,编译,汇编),”-c”选项表示不执行链接步骤。

  $(CC) $(CPPFLAGS) $(CFLAGS) example.c -c -o example.o

  #2,然后将目标文件连接为最终的结果(连接),”-o”选项用于指定输出文件的名字。

  $(CC) $(LDFLAGS) example.o -o example

  #有一些软件包一次完成四个步骤:

  $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) example.c -o example

  当然也有少数软件包不遵守这些约定俗成的规范,比如:

  #1,有些在命令行中漏掉应有的Makefile变量(注意:有些遗漏是故意的)

  $(CC) $(CFLAGS) example.c -c -o example.o

  $(CC) $(CPPFLAGS) example.c -c -o example.o

  $(CC) example.o -o example

  $(CC) example.c -o example

  #2,有些在命令行中增加了不必要的Makefile变量

  $(CC) $(CFLAGS) $(LDFLAGS) example.o -o example

  $(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) example.c -c -o example.o

  当然还有极个别软件包完全是”胡来”:乱用变量(增加不必要的又漏掉了应有的)者有之,不用$(CC)者有之,不一而足…..

   尽管将源代码编译为二进制文件的四个步骤由不同的程序(cpp,gcc/g++,as,ld)完成,但是事实上 cpp, as, ld 都是由 gcc/g++ 进行间接调用的。换句话说,控制了 gcc/g++ 就等于控制了所有四个步骤。从 Makefile 规则中的编译命令可以看出,编译工具的行为全靠 CC/CXX CPPFLAGS CFLAGS/CXXFLAGS LDFLAGS 这几个变量在控制。当然理论上控制编译工具行为的还应当有 AS ASFLAGS ARFLAGS 等变量,但是实践中基本上没有软件包使用它们。

   那么我们如何控制这些变量呢?一种简易的做法是首先设置与这些 Makefile 变量同名的环境变量并将它们 export 为全局,然后运行 configure 脚本,大多数 configure 脚本会使用这同名的环境变量代替 Makefile 中的值。但是少数 configure 脚本并不这样做(比如GCC-3.4.6和Binutils-2.16.1的脚本就不传递LDFLAGS),你必须手动编辑生成的 Makefile 文件,在其中寻找这些变量并修改它们的值,许多源码包在每个子文件夹中都有 Makefile 文件,真是一件很累人的事!

  CC 与 CXX

   这是 C 与 C++ 编译器命令。默认值一般是 “gcc” 与 “g++”。这个变量本来与优化没有关系,但是有些人因为担心软件包不遵守那些约定俗成的规范,害怕自己苦心设置的 CFLAGS/CXXFLAGS/LDFLAGS 之类的变量被忽略了,而索性将原本应当放置在其它变量中的选项一股老儿塞到 CC 或 CXX 中,比如:CC=”gcc -march=k8 -O2 -s”。这是一种怪异的用法,本文不提倡这种做法,而是提倡按照变量本来的含义使用变量。

  CPPFLAGS

  这是用于预处理阶段的选项。不过能够用于此变量的选项,看不出有哪个与优化相关。如果你实在想设一个,那就使用下面这两个吧:

  -DNDEBUG

  ”NDEBUG”是一个标准的 ANSI 宏,表示不进行调试编译。

  -D_FILE_OFFSET_BITS=64

  大多数包使用这个来提供大文件(>2G)支持。CFLAGS 与 CXXFLAGS

   CFLAGS 表示用于 C 编译器的选项,CXXFLAGS 表示用于 C++ 编译器的选项。这两个变量实际上涵盖了编译和汇编两个步骤。大多数程序和库在编译时默认的优化级别是”2″(使用”-O2″选项)并且带有调试符号来编 译,也就是 CFLAGS=”-O2 -g”, CXXFLAGS=$CFLAGS 。事实上,”-O2″已经启用绝大多数安全的优化选项了。另一方面,由于大部分选项可以同时用于这两个变量,所以仅在最后讲述只能用于其中一个变量的选 项。[提醒]下面所列选项皆为非默认选项,你只要按需添加即可。

  先说说”-O3″在”-O2″基础上增加的几项:

  -finline-functions

  允许编译器选择某些简单的函数在其被调用处展开,比较安全的选项,特别是在CPU二级缓存较大时建议使用。

  -funswitch-loops

  将循环体中不改变值的变量移动到循环体之外。

  -fgcse-after-reload

  为了清除多余的溢出,在重载之后执行一个额外的载入消除步骤。

  另外:

  -fomit-frame-pointer

   对于不需要栈指针的函数就不在寄存器中保存指针,因此可以忽略存储和检索地址的代码,同时对许多函数提供一个额外的寄存器。所有”-O”级别都打开它, 但仅在调试器可以不依靠栈指针运行时才有效。在AMD64平台上此选项默认打开,但是在x86平台上则默认关闭。建议显式的设置它。

  -falign-functions=N

  -falign-jumps=N

  -falign-loops=N

  -falign-labels=N

   这四个对齐选项在”-O2″中打开,其中的根据不同的平台N使用不同的默认值。如果你想指定不同于默认值的N,也可以单独指定。比如,对于L2- cache>=1M的cpu而言,指定 -falign-functions=64 可能会获得更好的性能。建议在指定了 -march 的时候不明确指定这里的值。

  调试选项:

  -fprofile-arcs

  在使用这一选项编译程序并运 行它以创建包含每个代码块的执行次数的文件后,程序可以再次使用 -fbranch-probabilities 编译,文件中的信息可以用来优化那些经常选取的分支。如果没有这些信息,gcc将猜测哪个分支将被经常运行以进行优化。这类优化信息将会存放在一个以源文 件为名字的并以”.da”为后缀的文件中。

  全局选项:

  -pipe

  在编译过程的不同阶段之间使用管道而非临时文件进行通信,可以加快编译速度。建议使用。

  目录选项:

  –sysroot=dir

   将dir作为逻辑根目录。比如编译器通常会在 /usr/include 和 /usr/lib 中搜索头文件和库,使用这个选项后将在 dir/usr/include 和 dir/usr/lib 目录中搜索。如果使用这个选项的同时又使用了 -isysroot 选项,则此选项仅作用于库文件的搜索路径,而 -isysroot 选项将作用于头文件的搜索路径。这个选项与优化无关,但是在 CLFS 中有着神奇的作用。

  代码生成选项:

  -fno-bounds-check

  关闭所有对数组访问的边界检查。该选项将提高数组索引的性能,但当超出数组边界时,可能会造成不可接受的行为。

  -freg-struct-return

  如果struct和union足够小就通过寄存器返回,这将提高较小结构的效率。如果不够小,无法容纳在一个寄存器中,将使用内存返回。建议仅在完全使用GCC编译的系统上才使用。

  -fpic

  生成可用于共享库的位置独立代码。所有的内部寻址均通过全局偏移表完成。要确定一个地址,需要将代码自身的内存位置作为表中一项插入。该选项产生可以在共享库中存放并从中加载的目标模块。

  -fstack-check

  为防止程序栈溢出而进行必要的检测,仅在多线程环境中运行时才可能需要它。

  -fvisibility=hidden

   设置默认的ELF镜像中符号的可见性为隐藏。使用这个特性可以非常充分的提高连接和加载共享库的性能,生成更加优化的代码,提供近乎完美的API输出和 防止符号碰撞。我们强烈建议你在编译任何共享库的时候使用该选项。参见 -fvisibility-inlines-hidden 选项。

  硬件体系结构相关选项[仅仅针对x86与x86_64]:

  -march=cpu-type

   为特定的cpu-type编译二进制代码(不能在更低级别的cpu上运行)。Intel可以用:pentium2, pentium3(=pentium3m), pentium4(=pentium4m), pentium-m, prescott, nocona, core2(GCC-4.3新增) 。AMD可以用:k6-2(=k6-3), athlon(=athlon-tbird), athlon-xp(=athlon-mp), k8(=opteron=athlon64=athlon-fx)

  -mfpmath=sse

  P3和athlon-xp级别及以上的cpu支持”sse”标量浮点指令。仅建议在P4和K8以上级别的处理器上使用该选项。

  -malign-double

  将double, long double, long long对齐于双字节边界上;有助于生成更高速的代码,但是程序的尺寸会变大,并且不能与未使用该选项编译的程序一起工作。

  -m128bit-long-double

  指定long double为128位,pentium以上的cpu更喜欢这种标准,并且符合x86-64的ABI标准,但是却不附合i386的ABI标准。

  -mregparm=N

  指定用于传递整数参数的寄存器数目(默认不使用寄存器)。0<=N<=3 ;注意:当N>0时你必须使用同一参数重新构建所有的模块,包括所有的库。

  -msseregparm

  使用SSE寄存器传递float和double参数和返回值。注意:当你使用了这个选项以后,你必须使用同一参数重新构建所有的模块,包括所有的库。

  -mmmx

  -msse

  -msse2

  -msse3

  -m3dnow

  -mssse3(没写错!GCC-4.3新增)

  -msse4.1(GCC-4.3新增)

  -msse4.2(GCC-4.3新增)

  -msse4(含4.1和4.2,GCC-4.3新增)

  是否使用相应的扩展指令集以及内置函数,按照自己的cpu选择吧!

  -maccumulate-outgoing-args

  指定在函数引导段中计算输出参数所需最大空间,这在大部分现代cpu中是较快的方法;缺点是会明显增加二进制文件尺寸。

  -mthreads

  支持Mingw32的线程安全异常处理。对于依赖于线程安全异常处理的程序,必须启用这个选项。使用这个选项时会定义”-D_MT”,它将包含使用选项”-lmingwthrd”连接的一个特殊的线程辅助库,用于为每个线程清理异常处理数据。

  -minline-all-stringops

  默认时GCC只将确定目的地会被对齐在至少4字节边界的字符串操作内联进程序代码。该选项启用更多的内联并且增加二进制文件的体积,但是可以提升依赖于高速 memcpy, strlen, memset 操作的程序的性能。

  -minline-stringops-dynamically

  GCC-4.3新增。对未知尺寸字符串的小块操作使用内联代码,而对大块操作仍然调用库函数,这是比”-minline-all-stringops”更聪明的策略。决定策略的算法可以通过”-mstringop-strategy”控制。

-momit-leaf-frame-pointer

  不为叶子函数在寄存器中保存栈指针,这样可以节省寄存器,但是将会使调试变的困难。注意:不要与 -fomit-frame-pointer 同时使用,因为会造成代码效率低下。

  -m64

  生成专门运行于64位环境的代码,不能运行于32位环境,仅用于x86_64[含EMT64]环境。

  -mcmodel=small

  [默认值]程序和它的符号必须位于2GB以下的地址空间。指针仍然是64位。程序可以静态连接也可以动态连接。仅用于x86_64[含EMT64]环境。

  -mcmodel=kernel

  内核运行于2GB地址空间之外。在编译linux内核时必须使用该选项!仅用于x86_64[含EMT64]环境。

  -mcmodel=medium

  程序必须位于2GB以下的地址空间,但是它的符号可以位于任何地址空间。程序可以静态连接也可以动态连接。注意:共享库不能使用这个选项编译!仅用于x86_64[含EMT64]环境。

  其它优化选项:

  -fforce-addr

  必须将地址复制到寄存器中才能对他们进行运算。由于所需地址通常在前面已经加载到寄存器中了,所以这个选项可以改进代码。

  -finline-limit=n

  对伪指令数超过n的函数,编译程序将不进行内联展开,默认为600。增大此值将增加编译时间和编译内存用量并且生成的二进制文件体积也会变大,此值不宜太大。

  -fmerge-all-constants

  试图将跨编译单元的所有常量值和数组合并在一个副本中。但是标准C/C++要求每个变量都必须有不同的存储位置,所以该选项可能会导致某些不兼容的行为。

  -fgcse-sm

  在全局公共子表达式消除之后运行存储移动,以试图将存储移出循环。gcc-3.4中曾属于”-O2″级别的选项。

  -fgcse-las

  在全局公共子表达式消除之后消除多余的在存储到同一存储区域之后的加载操作。gcc-3.4中曾属于”-O2″级别的选项。

  -floop-optimize

  已废除(GCC-4.1曾包含在”-O1″中)。

  -floop-optimize2

   使用改进版本的循环优化器代替原来”-floop-optimize”。该优化器将使用不同的选项(-funroll-loops, -fpeel-loops, -funswitch-loops, -ftree-loop-im)分别控制循环优化的不同方面。目前这个新版本的优化器尚在开发中,并且生成的代码质量并不比以前的版本高。已废除,仅存在 于GCC-4.1之前的版本中。

  -funsafe-loop-optimizations

  假定循环不会溢出,并且循环的退出条件不是无穷。这将可以在一个比较广的范围内进行循环优化,即使优化器自己也不能断定这样做是否正确。

  -fsched-spec-load

  允许一些装载指令执行一些投机性的动作。

  -ftree-loop-linear

  在trees上进行线型循环转换。它能够改进缓冲性能并且允许进行更进一步的循环优化。

  -fivopts

  在trees上执行归纳变量优化。

  -ftree-vectorize

  在trees上执行循环向量化。

  -ftracer

  执行尾部复制以扩大超级块的尺寸,它简化了函数控制流,从而允许其它的优化措施做的更好。据说挺有效。

  -funroll-loops

  仅对循环次数能够在编译时或运行时确定的循环进行展开,生成的代码尺寸将变大,执行速度可能变快也可能变慢。

  -fprefetch-loop-arrays

  生成数组预读取指令,对于使用巨大数组的程序可以加快代码执行速度,适合数据库相关的大型软件等。具体效果如何取决于代码。

  -fweb

  建立经常使用的缓存器网络,提供更佳的缓存器使用率。gcc-3.4中曾属于”-O3″级别的选项。

  -ffast-math

  违反IEEE/ANSI标准以提高浮点数计算速度,是个危险的选项,仅在编译不需要严格遵守IEEE规范且浮点计算密集的程序考虑采用。

  -fsingle-precision-constant

  将浮点常量作为单精度常量对待,而不是隐式地将其转换为双精度。

  -fbranch-probabilities

   在使用 -fprofile-arcs 选项编译程序并执行它来创建包含每个代码块执行次数的文件之后,程序可以利用这一选项再次编译,文件中所产生的信息将被用来优化那些经常发生的分支代码。 如果没有这些信息,gcc将猜测那一分支可能经常发生并进行优化。这类优化信息将会存放在一个以源文件为名字的并以”.da”为后缀的文件中。

  -frename-registers

  试图驱除代码中的假依赖关系,这个选项对具有大量寄存器的机器很有效。gcc-3.4中曾属于”-O3″级别的选项。

  -fbranch-target-load-optimize

  -fbranch-target-load-optimize2

  在执行序启动以及结尾之前执行分支目标缓存器加载最佳化。

  -fstack-protector

  在关键函数的堆栈中设置保护值。在返回地址和返回值之前,都将验证这个保护值。如果出现了缓冲区溢出,保护值不再匹配,程序就会退出。程序每次运行,保护值都是随机的,因此不会被远程猜出。

  -fstack-protector-all

  同上,但是在所有函数的堆栈中设置保护值。

  –param max-gcse-memory=xxM

  执行GCSE优化使用的最大内存量(xxM),太小将使该优化无法进行,默认为50M。

  –param max-gcse-passes=n

  执行GCSE优化的最大迭代次数,默认为 1。

  传递给汇编器的选项:

  -Wa,options

  options是一个或多个由逗号分隔的可以传递给汇编器的选项列表。其中的每一个均可作为命令行选项传递给汇编器。

  -Wa,–strip-local-absolute

  从输出符号表中移除局部绝对符号。

  -Wa,-R

  合并数据段和正文段,因为不必在数据段和代码段之间转移,所以它可能会产生更短的地址移动。

  -Wa,–64

  设置字长为64bit,仅用于x86_64,并且仅对ELF格式的目标文件有效。此外,还需要使用”–enable-64-bit-bfd”选项编译的BFD支持。

  -Wa,-march=CPU

  按照特定的CPU进行优化:pentiumiii, pentium4, prescott, nocona, core, core2; athlon, sledgehammer, opteron, k8 。仅可用于 CFLAGS 的选项:

  -fhosted

  按宿主环境编译,其中需要有完整的标准库,入口必须是main()函数且具有int型的返回值。内核以外几乎所有的程序都是如此。该选项隐含设置了 -fbuiltin,且与 -fno-freestanding 等价。

  -ffreestanding

  按独立环境编译,该环境可以没有标准库,且对main()函数没有要求。最典型的例子就是操作系统内核。该选项隐含设置了 -fno-builtin,且与 -fno-hosted 等价。

  仅可用于 CXXFLAGS 的选项:

  -fno-enforce-eh-specs

  C++标准要求强制检查异常违例,但是该选项可以关闭违例检查,从而减小生成代码的体积。该选项类似于定义了”NDEBUG”宏。

  -fno-rtti

  如果没有使用’dynamic_cast’和’typeid’,可以使用这个选项禁止为包含虚方法的类生成运行时表示代码,从而节约空间。此选项对于异常处理无效(仍然按需生成rtti代码)。

  -ftemplate-depth-n

  将最大模版实例化深度设为’n’,符合标准的程序不能超过17,默认值为500。

  -fno-optional-diags

  禁止输出诊断消息,C++标准并不需要这些消息。

  -fno-threadsafe-statics

  GCC自动在访问C++局部静态变量的代码上加锁,以保证线程安全。如果你不需要线程安全,可以使用这个选项。

  -fvisibility-inlines-hidden

  默认隐藏所有内联函数,从而减小导出符号表的大小,既能缩减文件的大小,还能提高运行性能,我们强烈建议你在编译任何共享库的时候使用该选项。参见 -fvisibility=hidden 选项。

  LDFLAGS

  LDFLAGS 是传递给连接器的选项。这是一个常被忽视的变量,事实上它对优化的影响也是很明显的。

  [提示]以下选项是在完整的阅读了ld-2.18文档之后挑选出来的选项。http://blog.chinaunix.net/u1/41220/showart_354602.html 有2.14版本的中文手册。

  -s

  删除可执行程序中的所有符号表和所有重定位信息。其结果与运行命令 strip 所达到的效果相同,这个选项是比较安全的。

  -Wl,options

  options是由一个或多个逗号分隔的传递给链接器的选项列表。其中的每一个选项均会作为命令行选项提供给链接器。

  -Wl,-On

  当n>0时将会优化输出,但是会明显增加连接操作的时间,这个选项是比较安全的。

  -Wl,–exclude-libs=ALL

  不自动导出库中的符号,也就是默认将库中的符号隐藏。

  -Wl,-m

  仿真连接器,当前ld所有可用的仿真可以通过”ld -V”命令获取。默认值取决于ld的编译时配置。

  -Wl,–sort-common

  把全局公共符号按照大小排序后放到适当的输出节,以防止符号间因为排布限制而出现间隙。

  -Wl,-x

  删除所有的本地符号。

  -Wl,-X

  删除所有的临时本地符号。对于大多数目标平台,就是所有的名字以’L’开头的本地符号。

  -Wl,-zcomberloc

  组合多个重定位节并重新排布它们,以便让动态符号可以被缓存。

  -Wl,–enable-new-dtags

  在ELF中创建新式的”dynamic tags”,但在老式的ELF系统上无法识别。

  -Wl,–as-needed

  移除不必要的符号引用,仅在实际需要的时候才连接,可以生成更高效的代码。

  -Wl,–no-define-common

  限制对普通符号的地址分配。该选项允许那些从共享库中引用的普通符号只在主程序中被分配地址。这会消除在共享库中的无用的副本的空间,同时也防止了在有多个指定了搜索路径的动态模块在进行运行时符号解析时引起的混乱。

  -Wl,–hash-style=gnu

  使用gnu风格的符号散列表格式。它的动态链接性能比传统的sysv风格(默认)有较大提升,但是它生成的可执行程序和库与旧的Glibc以及动态链接器不兼容。

  ——————————————————————————–

  最后说两个与优化无关的系统环境变量,因为会影响GCC编译程序的方式,下面两个是咱中国人比较关心的:

  LANG

  指定编译程序使用的字符集,可用于创建宽字符文件、串文字、注释;默认为英文。[目前只支持日文”C-JIS,C-SJIS,C-EUCJP”,不支持中文]

  LC_ALL

   指定多字节字符的字符分类,主要用于确定字符串的字符边界以及编译程序使用何种语言发出诊断消息;默认设置与LANG相同。中文相关的几 项:”zh_CN.GB2312 , zh_CN.GB18030 , zh_CN.GBK , zh_CN.UTF-8 , zh_TW.BIG5″。

出处:http://blog.chinaunix.net/u3/110004/showart_2146691.html