gcc

目 錄

  1. gcc
    1. makefile寫法
    2. gcc_egcs使用
    3. gdb使用
    4. gcc常用選項對代碼的影響
      1. 一般情況
      2. -O 編譯選項
      3. -O2 編譯選項
      4. -fomit-frame-pointer 編譯選項
      5. -fomit-frame-pointer && -O2
      6. -fPIC 編譯選項
      7. -static 編譯選項
    5. AT&T的彙編格式
    6. x86內聯彙編
      1. 簡述
      2. 內聯彙編
      3. 程序模板
      4. 操作數
      5. 修飾寄存器列表
      6. 操作數約束
      7. 示例
        1. 寄存器約束
        2. 匹配約束
        3. 內存操作數約束
        4. 修飾寄存器
    7. 不同的CPU下最佳編譯參數
    8. 代碼維護
      1. 簡單cvs
      2. automake
      3. diff
      4. rcs
      5. 內核重編譯常見故障
      6. cvs
      7. 共享庫工具
      8. 代碼優化
      9. GNU 編碼標準
      10. 書籍

gcc
[目錄]



makefile寫法
Abstract:

    在 Unix 上寫程式的人大概都碰過 Makefile,尤其是用 C 來開發程式的人。用 make來開發和編譯程式的確很方便,可是要寫出一個 Makefile就不簡單了。偏偏介紹 Makefile 的文件不多,GNU Make 那份印出來要幾百頁的文件,光看完 Overview 就快陣亡了,難怪許多
人聞 Unix 色變。

    本文將介紹如何利用 GNU Autoconf 及 Automake 這兩套軟體來協助我們『自動』產生 Makefile 檔,並且讓開發出來的軟體可以像 Apache, MySQL 和常見的 GNU 軟體一樣,只要會 ``./configure'', ``make'', ``make install'' 就可以把程式安裝到系統中。如果您有
心開發 Open Source 的軟體,或只是想在 Unix 系統下寫寫程式。希望這份介紹文件能幫助您輕鬆地進入 Unix Programming 的殿堂。

1. 簡介

    Makefile 基本上就是『目標』(target), 『關連』(dependencies) 和『動作』三者所組成的一連串規則。而 make 就會根據 Makefile 的規則來決定如何編譯 (compile) 和連結 (link) 程式。實際上,make 可做的不只是編譯和連結程式,例如 FreeBSD 的 port collect
ion 中, Makefile 還可以做到自動下載原始程式套件,解壓縮 (extract) ,修補 (patch),設定,然後編譯,安裝至系統中。

    Makefile 基本構造雖然簡單,但是妥善運用這些規則就也可以變出許多不同的花招。卻也因此,許多剛開始學習寫 Makefile 時會感到沒有規範可循,每個人寫出來的 Makefile 長得都不太一樣,不知道從何下手,而且常常會受限於自己的開發環境,只要環境變數不同或路
徑改一下,可能Makefile 就得跟着修改。雖然有 GNU Makefile Conventions (GNU Makefile 慣例) 訂出一些使用 GNU 程式設計時撰寫 Makefile 的一些標準和規範,但是內容很長而且很複雜, 並且經常做些調整,爲了減輕程式設計師維護 Makefile 的負擔,因此有了Automake。

    程式設計師只需寫一些預先定義好的巨集 (macro),交給 Automake 處理後會產生一個可供Autoconf 使用的 Makefile.in 檔。再配合利用Autoconf 產生的自動設定檔 configure即可產生一份符合 GNU Makefile慣例的 Makeifle 了。

2. 上路之前

    在開始試着用 Automake 之前,請先確認你的系統已經安裝以下的軟體:
1. GNU Automake
2. GNU Autoconf
3. GNU m4
4. perl
5. GNU Libtool (如果你需要產生 shared library)

    我會建議你最好也使用 GNU C/C++ 編譯器 、GNU Make 以及其它 GNU 的工具程式來做爲開發的環境,這些工具都是屬於 Open Source Software不僅免費而且功能強大。如果你是使用Red Hat Linux 可以找到所有上述軟體的 rpm 檔,FreeBSD 也有現成的 package 可以直
接安裝,或着你也可以自行下載這些軟體的原始檔回來 DIY。以下的範例是在 Red Hat Linux 5.2 + CLE2 的環境下所完成的。

3. 一個簡單的例子

    Automake 所產生的 Makefile 除了可以做到程式的編譯和連結,也已經把如何產生程式文件(如 manual page, info 檔及 dvi 檔) 的動作,還有把原始程式包裝起來以供散 的動作都考慮進去了,所以原始程式所存放的目錄架構最好符合 GNU 的標準慣例,接下來我拿hello.c 來做爲例子。

    在工作目錄下建立一個新的子目錄 ``devel'',再在 devel 下建立一個``hello'' 的子目錄,這個目錄將作爲我們存放 hello 這個程式及其相關檔案的地方:

% mkdir devel
% cd devel
% mkdir hello
% cd hello

用編輯器寫個 hello.c 檔,

 


    接下來就要用 Autoconf 及 Automake 來幫我們產生 Makefile 檔了,

1. 用 autoscan 產生一個 configure.in 的雛型,執行 autoscan 後會產生一個configure.scan 的檔案,我們可以用它做爲configure.in檔的藍本。

% autoscan
% ls
configure.scan hello.c

2. 編輯 configure.scan 檔,如下所示,並且把它的檔名改成configure.in
dnl Process this file with autoconf to produce a con figure script.
AC_INIT(hello.c)
AM_INIT_AUTOMAKE(hello, 1.0)
dnl Checks for programs.
AC_PROG_CC
dnl Checks for libraries.
dnl Checks for header files.
dnl Checks for typedefs, structures, and compiler ch aracteristics.
dnl Checks for library functions.
AC_OUTPUT(Makefile)

3. 執行 aclocal 和 autoconf ,分別會產生 aclocal.m4 及 configure 兩個檔案
% aclocal
% autoconf
% ls
aclocal.m4 configure configure.in hello.c

4. 編輯 Makefile.am 檔,內容如下
AUTOMAKE_OPTIONS= foreign
bin_PROGRAMS= hello
hello_SOURCES= hello.c

5. 執行 automake --add-missing ,Automake 會根據 Makefile.am 檔產生一些檔案,包含最重要的 Makefile.in
% automake --add-missing
automake: configure.in: installing `./install-sh'
automake: configure.in: installing `./mkinstalldirs'
automake: configure.in: installing `./missing'

6. 最後執行 ./configure ,
% ./configure
creating cache ./config.cache
checking for a BSD compatible install... /usr/bin/in stall -c
checking whether build environment is sane... yes
checking whether make sets ${MAKE}... yes
checking for working aclocal... found
checking for working autoconf... found
checking for working automake... found
checking for working autoheader... found
checking for working makeinfo... found
checking for gcc... gcc
checking whether the C compiler (gcc ) works... yes
checking whether the C compiler (gcc ) is a cross-co mpiler... no
checking whether we are using GNU C... yes
checking whether gcc accepts -g... yes
updating cache ./config.cache
creating ./config.status
creating Makefile

    現在你的目錄下已經產生了一個 Makefile 檔,下個 ``make'' 指令就可以開始編譯 hello.c 成執行檔,執行 ./hello 和 GNU 打聲招呼吧!

% make
gcc -DPACKAGE="hello" -DVERSION="1.0" -I. -I. -g -O2 -c he llo.c
gcc -g -O2 -o hello hello.o
% ./hello
Hello! GNU!

    你還可以試試 ``make clean'',''make install'',''make dist'' 看看會有什麼結果。你也可以把產生出來的 Makefile 秀給你的老闆,讓他從此對你刮目相看 :-)

4. 一探究竟

    上述產生 Makefile 的過程和以往自行編寫的方式非常不一樣,捨棄傳統自行定義 make 的規則,使用 Automake 只需用到一些已經定義好的巨集即可。我們把巨集及目標 (target)寫在 Makefile.am 檔內,Automake讀入 Makefile.am 檔後會把這一串已經定義好的巨集展
開並且產生對應的Makefile.in 檔, 然後再由 configure 這個 shell script 根據Makefile.in 產生適合的 Makefile。
    在此範例中可藉由 Autoconf 及 Automake 工具所產生的檔案有 configure.scan、aclocal.m4、configure、Makefile.in,需要我們加入設定者爲 configure.in 及 Makefile.am。

4.1 編輯 configure.in 檔

    Autoconf 是用來產生 'configure' 檔的工具。'configure' 是一個shell script,它可以自動設定原始程式以符合各種不同平臺上 Unix 系統的特性,並且根據系統叄數及環境產生合適的 Makefile 檔或是C 的標頭檔 (header file),讓原始程式可以很方便地在這些不同
的平臺上被編譯出來。Autoconf 會讀取 configure.in 檔然後產生 'configure' 這個shell script。

    configure.in 檔的內容是一連串 GNU m4 的巨集,這些巨集經過autoconf 處理後會變成檢查系統特徵的 shell script。configure.in 內巨集的順序並沒有特別的規定,但是每一個 configure.in 檔必須在所有巨集前加入 AC_INIT 巨集,然後在所有巨集的最後面加上 AC
_OUTPUT 巨集。我們可先用 autoscan 掃描原始檔以產生一個 configure.scan 檔,再對 configure.scan 做些修改成 configure.in 檔。在範例中所用到的巨集如下:

dnl
    這個巨集後面的字不會被處理,可視爲註解。

AC_INIT(FILE)
    這個巨集用來檢查原始碼所在的路徑,autoscan 會自動產生,我們不必修改它。

AM_INIT_AUTOMAKE(PACKAGE,VERSION)
    這是使用 Automake 所必備的巨集,PACKAGE 是我們所要產生軟體套件的名稱,VERSION 是版本編號。

AC_PROG_CC
    檢查系統可用的 C 編譯器,如果原始程式是用 C 寫的就需要這個巨集。

AC_OUTPUT(FILE)
    設定 configure 所要產生的檔案,如果是 Makefile 的話,configure 便會把它檢查出來的結果帶入 Makefile.in 檔然後產生
合適的 Makefile。

    實際上,我們使用 Automake 時,還須要一些其它的巨集,這些額外的巨集我們用 aclocal來幫我們產生。執行 aclocal 會產生 aclocal.m4檔,如果沒有特別的用途,我們可以不必修改它,用 aclocal 所產生的巨集會告訴 Automake 怎麼做。

    有了 configure.in 及 aclocal.m4 兩個檔案後,便可以執行 autoconf來產生 configure檔了。

4.2 編輯 Makefile.am 檔

    接下來我們要編輯 Makefile.am 檔,Automake 會根據 configure.in 中的巨集把Makefile.am 轉成 Makefile.in 檔。Makefile.am 檔定義我們所要產的目標:

AUTOMAKE_OPTIONS
    設定 automake 的選項。Automake 主要是幫助開發 GNU 軟體的人員維護軟體套件,所以在執行 automake 時,會檢查目錄下是否存在標準 GNU 軟體套件中應具備的文件檔案,例如 'NEWS'、'AUTHOR'、'ChangeLog' 等文件檔。設成 foreign 時,automake 會改用一般軟
體套件的標準來檢查。

bin_PROGRAMS
    定義我們所要產生的執行檔檔名。如果要產生多個執行檔,每個檔名用空白字元隔開。

hello_SOURCES
    定義 'hello' 這個執行檔所需要的原始檔。如果 'hello' 這個程式是由多個原始檔所產生,必須把它所用到的原始檔都列出來,以空白字元隔開。假設 'hello' 這個程式需要 'hello.c'、'main.c'、

'hello.h'
    三個檔案的話,則定義
        hello_SOURCES= hello.c main.c hello.h
    如果我們定義多個執行檔,則對每個執行檔都要定義相對的filename_SOURCES。

    編輯好 Makefile.am 檔,就可以用 automake --add-missing 來產生Makefile.in。加上 --add-missing 選項是告訴 automake 順便幫我們加入包裝一個軟體套件所必備的檔案。Automake 產生出來的 Makefile.in檔是完全符合 GNU Makefile 的慣例,我們只要執行 confi
gure 這個shell script 便可以產生合適的 Makefile 檔了。

4.3 使用 Makefile

    利用 configure 所產生的 Makefile 檔有幾個預設的目標可供使用,我們只拿其中幾個簡述如下:

make all
    產生我們設定的目標,即此範例中的執行檔。只打 make 也可以,此時會開始編譯原始碼,然後連結,並且產生執行檔。

make clean
    清除之前所編譯的執行檔及目的檔 (object file, *.o)。

make distclean
    除了清除執行檔和目的檔外,也把 configure 所產生的 Makefile也清除掉。

make install
    將程式安裝至系統中。如果原始碼編譯無誤,且執行結果正確,便可以把程式安裝至系統預設的執行檔存放路徑。如果我們用bin_PROGRAMS 巨集的話,程式會被安裝至 /usr/local/bin 這個目錄。

make dist
    將程式和相關的檔案包裝成一個壓縮檔以供散播 (distribution) 。執行完在目錄下會產生一個以 PACKAGE-VERSION.tar.gz 爲名稱的檔案。PACKAGE 和 VERSION 這兩個變數是根據 configure.in 檔中AM_INIT_AUTOMAKE(PACKAGE, VERSION) 的定義。在此範例中會產生
'hello-1.0.tar.gz' 的檔案。

make distcheck
    和 make dist 類似,但是加入檢查包裝後的壓縮檔是否正常。這個目標除了把程式和相關檔案包裝成 tar.gz 檔外,還會自動把這個壓
縮檔解開,執行 configure,並且進行 make all 的動作,確認編譯無誤後,會顯示這個 tar.gz 檔已經準備好可供散播了。這個檢查非
常有用,檢查過關的套件,基本上可以給任何一個具備 GNU 發展境的人去重新編譯。就 hello-1.tar.gz 這個範例而言,除了在 Red
Hat Linux 上,在 FreeBSD 2.2.x 版也可以正確地重新編譯。

    要注意的是,利用 Autoconf 及 Automake 所產生出來的軟體套件是可以在沒有安裝 Autoconf 及 Automake 的環境上使用的,因爲 configure 是一個 shell script,它己被設計可以在一般 Unix 的 sh 這個 shell 下執行。但是如果要修改 configure.in 及 Makefile.a
m 檔再產生新的configure 及 Makefile.in 檔時就一定要有 Autoconf 及 Automake 了。

5. 相關訊息

    Autoconf 和 Automake 功能十分強大,你可以從它們所附的 info 檔找到詳細的用法。你也可以從許多現存的 GNU 軟體或 Open Source 軟體中找到相關的 configure.in 或 Makefile.am 檔,它們是學習 Autoconf 及Automake 更多技巧的最佳範例。

    這篇簡介只用到了 Autoconf 及 Automake 的皮毛罷了,如果你有心加入Open Source 軟體開發的行列,希望這篇文件能幫助你對產生 Makefile有個簡單的依據。其它有關開發 GNU程式或 C 程式設計及 Makefile 的詳細運用及技巧,我建議你從 GNU Coding Standards3
(GNU 編碼標準規定) 讀起,裏面包含了 GNU Makefile 慣例,還有發展 GNU 軟體套件的標準程序和慣例。這些 GNU 軟體的線上說明文件可以在http://www.gnu.org/ 這個網站上找到。

6. 結語

    經由 Autoconf 及 Automake 的輔助,產生一個 Makefile 似乎不再像以前那麼困難了,而使用 Autoconf 也使得我們在不同平臺上或各家 Unix之間散播及編譯程式變得簡單,這對於在 Unix 系統上開發程式的人員來說減輕了許多負擔。妥善運用這些 GNU 的工具軟體,可
以幫助我們更容易去發展程式,而且更容易維護原始程式碼。

    一九九八年是 Open Source 運動風起雲涌的一年,許多 Open Source 的軟體普遍受到網路上大衆的歡迎和使用。感謝所有爲 Open Source 奉獻的人們,也希望藉由本文能吸引更多的人加入『自由』、『開放』的 OpenSource 行列。

About this document ...

輕輕鬆鬆產生 Makefile1

This document was generated using the LaTeX2HTML translator Version 98.2 beta6 (August 14th, 1998) Copyright (C) 1993, 1994, 1995, 1996, Nikos Drakos, ComputerBased Learning Unit, University of Leeds.Copyright (C) 1997, 1998, Ross Moore, Mathematics Department,Macquarie University, Sydney.

The command line arguments were:
latex2html -split 0 -show_section_numbers automake.tex
The translation was initiated by on 1999-02-11

Footnotes
... itle1
本文件使用 ChiLaTeX 製作。
... CLE2
CLE (Chinese Linux Extension,Linux 中文延伸套件),
http://cle.linux.org.tw/

... Standards3
GNU Coding Standards, Richard Stallman.



[目錄]



gcc_egcs使用
1.使用egcs

    Linux 中最重要的軟件開發工具是 GCC。GCC 是 GNU 的 C 和 C++ 編譯器。實際上,GCC 能夠編譯三種語言:C、C++ 和 Object C(C 語言的一種面向對象擴展)。利用 gcc 命令可同時編譯並連接 C 和 C++ 源程序。

        #DEMO#: hello.c

    如果你有兩個或少數幾個 C 源文件,也可以方便地利用 GCC 編譯、連接並生成可執行文件。例如,假設你有兩個源文件 main.c 和 factorial.c 兩個源文件,現在要編譯生成一個計算階乘的程序。

-----------------------
清單 factorial.c
-----------------------
#include <stdio.h>
#include <stdlib.h>

int factorial (int n)
{
    if (n <= 1)
        return 1;

    else
        return factorial (n - 1) * n;
}
-----------------------

-----------------------
清單  main.c
-----------------------
#include <stdio.h>
#include <stdlib.h>

int factorial (int n);

int main (int argc, char **argv)
{
    int n;

    if (argc < 2) {
        printf ("Usage: %s n/n", argv [0]);
        return -1;
    }
    else {
        n = atoi (argv[1]);
        printf ("Factorial of %d is %d./n", n, factorial (n));
    }

    return 0;
}
-----------------------

    利用如下的命令可編譯生成可執行文件,並執行程序:

$ gcc -o factorial main.c factorial.c
$ ./factorial 5
Factorial of 5 is 120.

    GCC 可同時用來編譯 C 程序和 C++ 程序。一般來說,C 編譯器通過源文件的後綴名來判斷是 C 程序還是 C++ 程序。在 Linux 中,C 源文件的後綴名爲 .c,而 C++ 源文件的後綴名爲 .C 或 .cpp。

    但是,gcc 命令只能編譯 C++ 源文件,而不能自動和 C++ 程序使用的庫連接。因此,通常使用 g++ 命令來完成 C++ 程序的編譯和連接,該程序會自動調用 gcc 實現編譯。假設我們有一個如下的 C++ 源文件(hello.C):

#include <iostream.h>

void main (void)
{
    cout << "Hello, world!" << endl;
}

    則可以如下調用 g++ 命令編譯、連接並生成可執行文件:

$ g++ -o hello hello.C
$ ./hello
Hello, world!

2.  gcc/egcs 的主要選項

                表 1-3  gcc 命令的常用選項
選項                解釋
-ansi               只支持 ANSI 標準的 C 語法。這一選項將禁止 GNU C 的某些特色,
                    例如 asm 或 typeof 關鍵詞。
-c                  只編譯並生成目標文件。
-DMACRO             以字符串“1”定義 MACRO 宏。
-DMACRO=DEFN        以字符串“DEFN”定義 MACRO 宏。
-E                  只運行 C 預編譯器。
-g                  生成調試信息。GNU 調試器可利用該信息。
-IDIRECTORY         指定額外的頭文件搜索路徑DIRECTORY。
-LDIRECTORY         指定額外的函數庫搜索路徑DIRECTORY。
-lLIBRARY           連接時搜索指定的函數庫LIBRARY。
-m486               針對 486 進行代碼優化。
-o FILE             生成指定的輸出文件。用在生成可執行文件時。
-O0                 不進行優化處理。
-O 或 -O1           優化生成代碼。
-O2                 進一步優化。
-O3                 比 -O2 更進一步優化,包括 inline 函數。
-shared             生成共享目標文件。通常用在建立共享庫時。
-static             禁止使用共享連接。
-UMACRO             取消對 MACRO 宏的定義。
-w                  不生成任何警告信息。
-Wall               生成所有警告信息。

#DEMO#



[目錄]



gdb使用
1.簡介

    GNU 的調試器稱爲 gdb,該程序是一個交互式工具,工作在字符模式。在 X Window 系統中,有一個 gdb 的前端圖形工具,稱爲 xxgdb。gdb 是功能強大的調試程序,可完成如下的調試任務:

* 設置斷點;
* 監視程序變量的值;
* 程序的單步執行;
* 修改變量的值。
    在可以使用 gdb 調試程序之前,必須使用 -g 選項編譯源文件。可在 makefile 中如下定義CFLAGS 變量:

CFLAGS = -g
運行 gdb 調試程序時通常使用如下的命令:

gdb progname

    在 gdb 提示符處鍵入help,將列出命令的分類,主要的分類有:

* aliases:命令別名
* breakpoints:斷點定義;
* data:數據查看;
* files:指定並查看文件;
* internals:維護命令;
* running:程序執行;
* stack:調用棧查看;
* statu:狀態查看;
* tracepoints:跟蹤程序執行。

鍵入 help 後跟命令的分類名,可獲得該類命令的詳細清單。

2.gdb 的常用命令
                表 1-4  常用的 gdb 命令
命令                        解釋
break NUM               在指定的行上設置斷點。
bt                      顯示所有的調用棧幀。該命令可用來顯示函數的調用順序。
clear                   刪除設置在特定源文件、特定行上的斷點。其用法爲:clear FILENAME:NUM。
continue                繼續執行正在調試的程序。該命令用在程序由於處理信號或斷點而
                        導致停止運行時。
display EXPR            每次程序停止後顯示錶達式的值。表達式由程序定義的變量組成。
file FILE               裝載指定的可執行文件進行調試。
help NAME               顯示指定命令的幫助信息。
info break              顯示當前斷點清單,包括到達斷點處的次數等。
info files              顯示被調試文件的詳細信息。
info func               顯示所有的函數名稱。
info local              顯示當函數中的局部變量信息。
info prog               顯示被調試程序的執行狀態。
info var                顯示所有的全局和靜態變量名稱。
kill                    終止正被調試的程序。
list                    顯示源代碼段。
make                    在不退出 gdb 的情況下運行 make 工具。
next                    在不單步執行進入其他函數的情況下,向前執行一行源代碼。
print EXPR              顯示錶達式 EXPR 的值。

3.gdb 使用範例

-----------------
清單  一個有錯誤的 C 源程序 bugging.c
-----------------
#include <stdio.h>
#include <stdlib.h>

static char buff [256];
static char* string;
int main ()
{

    printf ("Please input a string: ");
    gets (string);

    printf ("/nYour string is: %s/n", string);
}
-----------------

    上面這個程序非常簡單,其目的是接受用戶的輸入,然後將用戶的輸入打印出來。該程序使用了一個未經過初始化的字符串地址 string,因此,編譯並運行之後,將出現 Segment Fault 錯誤:

$ gcc -o test -g test.c
$ ./test
Please input a string: asfd
Segmentation fault (core dumped)

爲了查找該程序中出現的問題,我們利用 gdb,並按如下的步驟進行:

1.運行 gdb bugging 命令,裝入 bugging 可執行文件;
2.執行裝入的 bugging 命令;
3.使用 where 命令查看程序出錯的地方;
4.利用 list 命令查看調用 gets 函數附近的代碼;
5.唯一能夠導致 gets 函數出錯的因素就是變量 string。用 print 命令查看 string 的值;
6.在 gdb 中,我們可以直接修改變量的值,只要將 string 取一個合法的指針值就可以了,爲
此,我們在第 11 行處設置斷點;
7.程序重新運行到第 11 行處停止,這時,我們可以用 set variable 命令修改 string 的取值;
8.然後繼續運行,將看到正確的程序運行結果。



[目錄]



gcc常用選項對代碼的影響
by alert7
2001-12-21
測試環境 redhat 6.2

★ 前言
    本文討論gcc的一些常用編譯選項對代碼的影響。當然代碼變了,它的內存佈局也就會變了,隨之exploit也就要做相應的變動。
gcc的編譯選項實在太多,本文檢了幾個最常用的選項。

★ 演示程序
[alert7@redhat62 alert7]$ cat > test.c
#include
void hi(void)
{
printf("hi");
}
int main(int argc, char *argv[])
{
        hi();
        return 0;
}



[目錄]



一般情況
★ 一般情況
[alert7@redhat62 alert7]$ gcc -o test test.c
[alert7@redhat62 alert7]$ wc -c test
  11773 test
[alert7@redhat62 alert7]$ gdb -q test
(gdb) disass main
Dump of assembler code for function main:
0x80483e4 :       push   %ebp
0x80483e5 :     mov    %esp,%ebp
0x80483e7 :     call   0x80483d0
0x80483ec :     xor    %eax,%eax
0x80483ee :    jmp    0x80483f0
0x80483f0 :    leave
0x80483f1 :    ret
....
End of assembler dump.
(gdb) disass hi
Dump of assembler code for function hi:
0x80483d0 :        push   %ebp
0x80483d1 :       mov    %esp,%ebp
0x80483d3 :       push   $0x8048450
0x80483d8 :       call   0x8048308
0x80483dd :      add    $0x4,%esp
0x80483e0 :      leave
0x80483e1 :      ret
0x80483e2 :      mov    %esi,%esi
End of assembler dump.
來看看部分的內存映象
                   (內存高址)
                              +--------+
                              |bffffbc4| argv的地址(即argv[0]的地址)
                   0xbffffb84 +--------+
                              |00000001| argc的值
                   0xbffffb80 +--------+
                              |400309cb|main的返回地址
                   0xbffffb7c +--------+ <-- 調用main函數前的esp
                              |bffffb98| 調用main函數前的ebp
                   0xbffffb78 +--------+ <-- main函數的ebp
                              |080483ec| hi()的返回地址
                   0xbffffb74 +--------+
                              |bffffb78| 調用hi()前的esp
                   0xbffffb70 +--------+
                              |08048450| "hi"的地址
                   0xbffffb6c +--------+
                              | ...... |
                   (內存低址)
leave    指令所做的操作相當於MOV ESP,EBP 然後 POP EBP
ret    指令所做的操作相當於POP EIP



[目錄]



-O 編譯選項
★ -O 編譯選項
With `-O', the compiler tries to reduce code size and execution time.
When you specify `-O', the two options `-fthread-jumps' and
`-fdefer-pop' are turned  on
優化,減少代碼大小和執行的時間
[alert7@redhat62 alert7]$ gcc -O -o test test.c
[alert7@redhat62 alert7]$ wc -c test
  11757 test
[alert7@redhat62 alert7]$ gdb -q test
(gdb) disass main
Dump of assembler code for function main:
0x80483d8 :       push   %ebp
0x80483d9 :     mov    %esp,%ebp
0x80483db :     call   0x80483c8
0x80483e0 :     xor    %eax,%eax
0x80483e2 :    leave
0x80483e3 :    ret
0x80483e4 :    nop
...
End of assembler dump.
(gdb) disass hi
Dump of assembler code for function hi:
0x80483c8 :        push   %ebp
0x80483c9 :       mov    %esp,%ebp
0x80483cb :       push   $0x8048440
0x80483d0 :       call   0x8048308
0x80483d5 :      leave
0x80483d6 :      ret
0x80483d7 :      nop
End of assembler dump.

    在main()中,把一條jmp指令優化掉了,很顯然,這條指令是可以不需要的。
    在hi()中,把add $0x4,%esp優化掉了,這會不會使stack不平衡呢?

    來看看部分的內存映象

                   (內存高址)
                              +--------+
                              |bffffbc4| argv的地址(即argv[0]的地址)
                   0xbffffb84 +--------+
                              |00000001| argc的值
                   0xbffffb80 +--------+
                              |400309cb|main的返回地址
                   0xbffffb7c +--------+ <-- 調用main函數前的esp
                              |bffffb98| 調用main函數前的ebp
                   0xbffffb78 +--------+ <-- main函數的ebp
                              |080483e0| hi()的返回地址
                   0xbffffb74 +--------+
                              |bffffb78| 調用hi()前的esp
                   0xbffffb70 +--------+
                              |08048440| "hi"的地址
                   0xbffffb6c +--------+
                              | ...... |
                   (內存低址)
    leave指令所做的操作相當於把MOV ESP,EBP 然後 POP EBP。看到leave指令操作了沒有,先把ebp-->esp,再pop ebp,這樣即使在過程內堆棧的esp,ebp是不平衡的,但只要返回時候碰到leave指令就會平衡了,所以把add $0x4,%esp優化掉也是沒有問題的。



[目錄]



-O2 編譯選項
★ -O2 編譯選項
-O2
    Optimize  even more.  Nearly all supported optimizations that do
    not involve a space-speed tradeoff are performed.  Loop unrolling
    and function inlining are not done, for example.  As compared to -O,
    this option increases both compilation time and the performance of
    the generated code.
[alert7@redhat62 alert7]$ gcc -O2 -o test test.c
[alert7@redhat62 alert7]$ wc -c test
  11757 test
[alert7@redhat62 alert7]$ gdb -q test
(gdb) disass main
Dump of assembler code for function main:
0x80483d8 :       push   %ebp
0x80483d9 :     mov    %esp,%ebp
0x80483db :     call   0x80483c8
0x80483e0 :     xor    %eax,%eax
0x80483e2 :    leave
0x80483e3 :    ret
...
0x80483ef :    nop
End of assembler dump.
(gdb) disass hi
Dump of assembler code for function hi:
0x80483c8 :        push   %ebp
0x80483c9 :       mov    %esp,%ebp
0x80483cb :       push   $0x8048440
0x80483d0 :       call   0x8048308
0x80483d5 :      leave
0x80483d6 :      ret
0x80483d7 :      nop
End of assembler dump.
由於程序比較簡單,再優化也沒有好優化的了,所以跟-O出來的一樣。


[目錄]



-fomit-frame-pointer 編譯選項
★ -fomit-frame-pointer 編譯選項
-fomit-frame-pointer
              Don't keep the frame pointer in a register for functions
          that don't need one.  This avoids the  instructions to save,
          set up and restore frame pointers; it also makes an extra
          register available in many functions.  It also makes
          debugging impossible on most machines.

    忽略幀指針。這樣在程序就不需要保存,安裝,和恢復ebp了。這樣ebp也就是一個free的register了,在函數中就可以隨便使用了。

[alert7@redhat62 alert7]$ gcc -fomit-frame-pointer -o test test.c
[alert7@redhat62 alert7]$ wc -c test
  11773 test
[alert7@redhat62 alert7]$ gdb -q test
(gdb) disass main
Dump of assembler code for function main:
0x80483e0 :       call   0x80483d0
0x80483e5 :     xor    %eax,%eax
0x80483e7 :     jmp    0x80483f0
0x80483e9 :     lea    0x0(%esi,1),%esi
0x80483f0 :    ret
....
End of assembler dump.
(gdb) disass hi
Dump of assembler code for function hi:
0x80483d0 :        push   $0x8048450
0x80483d5 :       call   0x8048308
0x80483da :      add    $0x4,%esp
0x80483dd :      ret
0x80483de :      mov    %esi,%esi
End of assembler dump.
在main()和hi()中都去掉了以下指令
push   %ebp
mov    %esp,%ebp//這兩條指令安裝
leave//這條指令恢復
來看看部分的內存映象
                   (內存高址)
                              +--------+
                              |bffffbc4| argv的地址(即argv[0]的地址)
                   0xbffffb84 +--------+
                              |00000001| argc的值
                   0xbffffb80 +--------+
                              |400309cb|main的返回地址
                   0xbffffb7c +--------+
                              |080483e5| hi()的返回地址
                   0xbffffb78 +--------+
                              |08048450|  "hi"字符串的地址
                   0xbffffb74 +--------+
                              | ...... |
                   (內存低址)
沒有保存上層執行環境的ebp.



[目錄]



-fomit-frame-pointer && -O2
★ -fomit-frame-pointer && -O2
-fomit-frame-pointer編譯選項去掉了
push   %ebp
mov    %esp,%ebp//這兩條指令安裝
leave//這條指令恢復
-O2編譯選項去掉了
add    $0x4,%esp
兩個加起來會不會這四條指令一起去掉,從而使stack不平衡呢?
[alert7@redhat62 alert7]$ gcc -fomit-frame-pointer -O2 -o test test.c
[alert7@redhat62 alert7]$ wc -c test
  11741 test
[alert7@redhat62 alert7]$ gdb -q test
(gdb) disass main
Dump of assembler code for function main:
0x80483d8 :       call   0x80483c8
0x80483dd :     xor    %eax,%eax
0x80483df :     ret
End of assembler dump.
(gdb) disass hi
Dump of assembler code for function hi:
0x80483c8 :        push   $0x8048430
0x80483cd :       call   0x8048308
0x80483d2 :      add    $0x4,%esp
0x80483d5 :      ret
0x80483d6 :      mov    %esi,%esi
End of assembler dump.
來看看部分的內存映象
                   (內存高址)
                              +--------+
                              |bffffbc4| argv的地址(即argv[0]的地址)
                   0xbffffb84 +--------+
                              |00000001| argc的值
                   0xbffffb80 +--------+
                              |400309cb|main的返回地址
                   0xbffffb7c +--------+
                              |080483dd| hi()的返回地址
                   0xbffffb78 +--------+
                              |08048430|  "hi"字符串的地址
                   0xbffffb74 +--------+
                              | ...... |
                   (內存低址)
此時就沒有把add    $0x4,%esp優化掉,如果優化掉的話,整個stack就
會變的不平衡,從而會導致程序出錯。



[目錄]



-fPIC 編譯選項
★ -fPIC 編譯選項
-fPIC    If  supported for the target machine, emit position-independent
    code, suitable for dynamic linking,even if branches need large
    displacements.

    產生位置無關代碼(PIC),一般創建共享庫時用到。
    在x86上,PIC的代碼的符號引用都是通過ebx進行操作的。

[alert7@redhat62 alert7]$ gcc -fPIC -o test test.c
[alert7@redhat62 alert7]$ wc -c test
  11805 test
[alert7@redhat62 alert7]$ gdb -q test
(gdb) disass main
Dump of assembler code for function main:
0x80483f8 :       push   %ebp
0x80483f9 :     mov    %esp,%ebp
0x80483fb :     push   %ebx
0x80483fc :     call   0x8048401
0x8048401 :     pop    %ebx//取得該指令的地址
0x8048402 :    add    $0x1093,%ebx//此時ebx裏面存放着是GOT表的地址
0x8048408 :    call   0x80483d0
0x804840d :    xor    %eax,%eax
0x804840f :    jmp    0x8048411
0x8048411 :    mov    0xfffffffc(%ebp),%ebx
0x8048414 :    leave
0x8048415 :    ret
...
End of assembler dump.
(gdb) disass hi
Dump of assembler code for function hi:
0x80483d0 :        push   %ebp
0x80483d1 :       mov    %esp,%ebp
0x80483d3 :       push   %ebx
0x80483d4 :       call   0x80483d9
0x80483d9 :       pop    %ebx
0x80483da :      add    $0x10bb,%ebx
0x80483e0 :      lea    0xffffefdc(%ebx),%edx
0x80483e6 :      mov    %edx,%eax
0x80483e8 :      push   %eax
0x80483e9 :      call   0x8048308
0x80483ee :      add    $0x4,%esp
0x80483f1 :      mov    0xfffffffc(%ebp),%ebx
0x80483f4 :      leave
0x80483f5 :      ret
0x80483f6 :      mov    %esi,%esi
End of assembler dump.
來看看部分的內存映象

    (內存高址)
              +--------+
              |bffffbc4| argv的地址(即argv[0]的地址)
   0xbffffb84 +--------+
              |00000001| argc的值
   0xbffffb80 +--------+
              |400309cb|main的返回地址
   0xbffffb7c +--------+ <-- 調用main函數前的esp
              |bffffb98| 調用main函數前的ebp
   0xbffffb78 +--------+ <-- main函數的ebp
              |401081ec| 保存的ebx
   0xbffffb74 +--------+
              |0804840d| (存放過call 0x8048401的下一條指令地址)
   0xbffffb70 +--------+
              |bffffb78| 調用hi()前的esp
   0xbffffb6c +--------+
              |08049494| GOT表地址
   0xbffffb68 +--------+
              |08048470|(存放過call 0x80483d9的下一條指令地址)
   0xbffffb64 +--------+
              | ...... |
     (內存低址)



[目錄]



-static 編譯選項
★ -static 編譯選項
-static
    On systems that support dynamic linking, this prevents
    linking with the shared libraries.  On other  systems,
    this option has no effect.
把一些函數都靜態的編譯到程序中,而無需動態鏈接了。
[alert7@redhat62 alert7]$ gcc -o test -static test.c
[alert7@redhat62 alert7]$ wc -c test
962808 test
[alert7@redhat62 alert7]$ gdb -q test
(gdb) disass main
Dump of assembler code for function main:
0x80481b4 :       push   %ebp
0x80481b5 :     mov    %esp,%ebp
0x80481b7 :     call   0x80481a0
0x80481bc :     xor    %eax,%eax
0x80481be :    jmp    0x80481c0
0x80481c0 :    leave
0x80481c1 :    ret
...
End of assembler dump.
(gdb) disass hi
Dump of assembler code for function hi:
0x80481a0 :        push   %ebp
0x80481a1 :       mov    %esp,%ebp
0x80481a3 :       push   $0x8071528
0x80481a8 :       call   0x804865c
0x80481ad :      add    $0x4,%esp
0x80481b0 :      leave
0x80481b1 :      ret
0x80481b2 :      mov    %esi,%esi
End of assembler dump.
[alert7@redhat62 alert7]$ ldd test

        not a dynamic executable
-static出來的代碼已經沒有PLT了,GOT雖然有,已經全部爲0了。



[目錄]



AT&T的彙編格式
一 基本語法

語法上主要有以下幾個不同.

★ 寄存器命名原則

AT&T: %eax Intel: eax

★源/目的操作數順序

AT&T: movl %eax,%ebx Intel: mov ebx,eax

★常數/立即數的格式

AT&T: movl $_value,%ebx Intel: mov eax,_value
把_value的地址放入eax寄存器

AT&T: movl $0xd00d,%ebx Intel: mov ebx,0xd00d

★ 操作數長度標識

AT&T: movw %ax,%bx Intel: mov bx,ax

★尋址方式

AT&T: immed32(basepointer,indexpointer,indexscale)
Intel: [basepointer + indexpointer*indexscale + imm32)

Linux工作於保護模式下,用的是32位線性地址,所以在計算地址時不用考慮segment:offset的問題.上式中的地址應爲:
imm32 + basepointer + indexpointer*indexscale

下面是一些例子:

★直接尋址

AT&T: _booga ; _booga是一個全局的C變量

注意加上$是表示地址引用,不加是表示值引用.
注:對於局部變量,可以通過堆棧指針引用.

Intel: [_booga]

★寄存器間接尋址

AT&T: (%eax)
Intel: [eax]

★變址尋址

AT&T: _variable(%eax)
Intel: [eax + _variable]

AT&T: _array(,%eax,4)
Intel: [eax*4 + _array]

AT&T: _array(%ebx,%eax,8)
Intel: [ebx + eax*8 + _array]

二 基本的行內彙編

    ·基本的行內彙編很簡單,一般是按照下面的格式:
        asm("statements");
例如:asm("nop"); asm("cli");

    ·asm 和 __asm__是完全一樣的.

    ·如果有多行彙編,則每一行都要加上 "/n/t"
例如:

asm( "pushl %eax/n/t"
"movl $0,%eax/n/t"
"popl %eax");

    實際上gcc在處理彙編時,是要把asm(...)的內容"打印"到彙編文件中,所以格式控制字符是必要的.

再例如:
asm("movl %eax,%ebx");
asm("xorl %ebx,%edx");
asm("movl $0,_booga);

    在上面的例子中,由於我們在行內彙編中改變了edx和ebx的值,但是由於gcc的特殊的處理方法,即先形成彙編文件,再交給GAS去彙編,所以GAS並不知道我們已經改變了edx和ebx的值,如果程序的上下文需要edx或ebx作暫存,這樣就會引起嚴重的後果.對於變量_booga也存在一樣的問題.爲了解決這個問題,就要用到擴展的行內彙編語法.

三 擴展的行內彙編

    擴展的行內彙編類似於Watcom.

    基本的格式是:
asm ( "statements" : output_regs : input_regs : clobbered_regs);
clobbered_regs指的是被改變的寄存器.

下面是一個例子(爲方便起見,我使用全局變量):

int count=1;
int value=1;
int buf[10];
void main()
{
asm(
"cld /n/t"
"rep /n/t"
"stosl"
:
: "c" (count), "a" (value) , "D" (buf[0])
: "%ecx","%edi" );
}

得到的主要彙編代碼爲:

movl count,%ecx
movl value,%eax
movl buf,%edi
#APP
cld
rep
stosl
#NO_APP

    cld,rep,stos就不用多解釋了.這幾條語句的功能是向buf中寫上count個value值.冒號後的語句指明輸入,輸出和被改變的寄存器.通過冒號以後的語句,編譯器就知道你的指令需要和改變哪些寄存器,從而可以優化寄存器的分配.
    其中符號"c"(count)指示要把count的值放入ecx寄存器

類似的還有:

a eax
b ebx
c ecx
d edx
S esi
D edi
I 常數值,(0 - 31)
q,r 動態分配的寄存器
g eax,ebx,ecx,edx或內存變量
A 把eax和edx合成一個64位的寄存器(use long longs)

我們也可以讓gcc自己選擇合適的寄存器.
如下面的例子:
asm("leal (%1,%1,4),%0"
: "=r" (x)
: "0" (x) );

這段代碼實現5*x的快速乘法.
得到的主要彙編代碼爲:
movl x,%eax
#APP
leal (%eax,%eax,4),%eax
#NO_APP
movl %eax,x

幾點說明:

1.使用q指示編譯器從eax,ebx,ecx,edx分配寄存器.使用r指示編譯器從eax,ebx,ecx,edx,esi,edi分配寄存器.

2.我們不必把編譯器分配的寄存器放入改變的寄存器列表,因爲寄存器已經記住了它們.

3."="是標示輸出寄存器,必須這樣用.

4.數字%n的用法:

    數字表示的寄存器是按照出現和從左到右的順序映射到用"r"或"q"請求的寄存器.如果我們要重用"r"或"q"請求的寄存器的話,就可以使用它們.

5.如果強制使用固定的寄存器的話,如不用%1,而用ebx,則asm("leal (%%ebx,%%ebx,4),%0"

: "=r" (x)
: "0" (x) );

注意要使用兩個%,因爲一個%的語法已經被%n用掉了.

下面可以來解釋letter 4854-4855的問題:


1、變量加下劃線和雙下劃線有什麼特殊含義嗎?
    加下劃線是指全局變量,但我的gcc中加不加都無所謂.

2、以上定義用如下調用時展開會是什麼意思?
#define _syscall1(type,name,type1,arg1) /
type name(type1 arg1) /
{ /
long __res; /
/* __res應該是一個全局變量 */
__asm__ volatile ("int $0x80" /
/* volatile 的意思是不允許優化,使編譯器嚴格按照你的彙編代碼彙編*/
: "=a" (__res) /
/* 產生代碼 movl %eax, __res */
: "0" (__NR_##name),"b" ((long)(arg1))); /
/* 如果我沒記錯的話,這裏##指的是兩次宏展開.
  即用實際的系統調用名字代替"name",然後再把__NR_...展開.
  接着把展開的常數放入eax,把arg1放入ebx */
if (__res >= 0) /
return (type) __res; /
errno = -__res; /
return -1; /
}



[目錄]



x86內聯彙編
    本文提供了在 Linux 平臺上使用和構造 x86 內聯彙編的概括性介紹。他介紹了內聯彙編及其各種用法的基礎知識,提供了一些基本的內聯彙編編碼指導,並解釋了在 Linux 內核中內聯彙編代碼的一些實例。
    如果您是 Linux 內核的開發人員,您會發現自己經常要對與體系結構高度相關的功能進行編碼或優化代碼路徑。您很可能是通過將彙編語言指令插入到 C 語句的中間(又稱爲內聯彙編的一種方法)來執行這些任務的。讓我們看一下 Linux 中內聯彙編的特定用法。(我們將討論限制在 IA32 彙編。)[目錄]



簡述
GNU 彙編程序簡述
    讓我們首先看一下 Linux 中使用的基本彙編程序語法。GCC(用於 Linux 的 GNU C 編譯器)使用 AT&T 彙編語法。下面列出了這種語法的一些基本規則。(該列表肯定不完整;只包括了與內聯彙編相關的那些規則。)

寄存器命名
    寄存器名稱有 % 前綴。即,如果必須使用 eax,它應該用作 %eax。

源操作數和目的操作數的順序
    在所有指令中,先是源操作數,然後纔是目的操作數。這與將源操作數放在目的操作數之後的 Intel 語法不同。
    mov %eax, %ebx, transfers the contents of eax to ebx.

操作數大小
    根據操作數是字節 (byte)、字 (word) 還是長型 (long),指令的後綴可以是 b、w 或 l。這並不是強制性的;GCC 會嘗試通過讀取操作數來提供相應的後綴。但手工指定後綴可以改善代碼的可讀性,並可以消除編譯器猜測不正確的可能性。

    movb %al, %bl -- Byte move
    movw %ax, %bx -- Word move
    movl %eax, %ebx -- Longword move

立即操作數
    通過使用 $ 指定直接操作數。

    movl $0xffff, %eax -- will move the value of 0xffff into eax register.

間接內存引用
    任何對內存的間接引用都是通過使用 ( ) 來完成的。

    movb (%esi), %al -- will transfer the byte in the memory
    pointed by esi into al register



[目錄]



內聯彙編
內聯彙編
    GCC 爲內聯彙編提供特殊結構,它具有以下格式:
    asm ( assembler template
        : output operands               (optional)
        : input operands                (optional)
        : list of clobbered registers   (optional)
        );

    本例中,彙編程序模板由彙編指令組成。輸入操作數是充當指令輸入操作數使用的 C 表達式。輸出操作數是將對其執行彙編指令輸出的 C 表達式。

    內聯彙編的重要性體現在它能夠靈活操作,而且可以使其輸出通過 C 變量顯示出來。因爲它具有這種能力,所以 "asm" 可以用作彙編指令和包含它的 C 程序之間的接口。

    一個非常基本但很重要的區別在於簡單內聯彙編只包括指令,而擴展內聯彙編包括操作數。要說明這一點,考慮以下示例:

內聯彙編的基本要素

{
    int a=10, b;
    asm ("movl %1, %%eax;
          movl %%eax, %0;"
          :"=r"(b)  /* output */
          :"r"(a)       /* input */
         :"%eax"); /* clobbered register */
}

    在上例中,我們使用匯編指令使 "b" 的值等於 "a"。請注意以下幾點:
    "b" 是輸出操作數,由 %0 引用,"a" 是輸入操作數,由 %1 引用。
    "r" 是操作數的約束,它指定將變量 "a" 和 "b" 存儲在寄存器中。請注意,輸出操作數約束應該帶有一個約束脩飾符 "=",指定它是輸出操作數。
    要在 "asm" 內使用寄存器 %eax,%eax 的前面應該再加一個 %,換句話說就是 %%eax,因爲 "asm" 使用 %0、%1 等來標識變量。任何帶有一個 % 的數都看作是輸入/輸出操作數,而不認爲是寄存器。
第三個冒號後的修飾寄存器 %eax 告訴將在 "asm" 中修改 GCC %eax 的值,這樣 GCC 就不使用該寄存器存儲任何其它的值。
    movl %1, %%eax 將 "a" 的值移到 %eax 中,movl %%eax, %0 將 %eax 的內容移到 "b" 中。
因爲 "b" 被指定成輸出操作數,因此當 "asm" 的執行完成後,它將反映出更新的值。換句話說,對 "asm" 內 "b" 所做的更改將在 "asm" 外反映出來。


[目錄]



程序模板
    彙編程序模板是一組插入到 C 程序中的彙編指令(可以是單個指令,也可以是一組指令)。每條指令都應該由雙引號括起,或者整組指令應該由雙引號括起。每條指令還應該用一個定界符結尾。有效的定界符爲新行 (/n) 和分號 (;)。 '/n' 後可以跟一個 tab(/t) 作爲格式化符號,增加 GCC 在彙編文件中生成的指令的可讀性。 指令通過數 %0、%1 等來引用 C 表達式(指定爲操作數)。

    如果希望確保編譯器不會在 "asm" 內部優化指令,可以在 "asm" 後使用關鍵字 "volatile"。如果程序必須與 ANSI C 兼容,則應該使用 __asm__ 和 __volatile__,而不是 asm 和 volatile。



[目錄]



操作數
   C 表達式用作 "asm" 內的彙編指令操作數。在彙編指令通過對 C 程序的 C 表達式進行操作來執行有意義的作業的情況下,操作數是內聯彙編的主要特性。

    每個操作數都由操作數約束字符串指定,後面跟用括弧括起的 C 表達式,例如:"constraint" (C expression)。操作數約束的主要功能是確定操作數的尋址方式。

    可以在輸入和輸出部分中同時使用多個操作數。每個操作數由逗號分隔開。

    在彙編程序模板內部,操作數由數字引用。如果總共有 n 個操作數(包括輸入和輸出),那麼第一個輸出操作數的編號爲 0,逐項遞增,最後那個輸入操作數的編號爲 n-1。總操作數的數目限制在 10,如果機器描述中任何指令模式中的最大操作數數目大於 10,則使用後者作爲限制。



[目錄]



修飾寄存器列表
    如果 "asm" 中的指令指的是硬件寄存器,可以告訴 GCC 我們將自己使用和修改它們。這樣,GCC 就不會假設它裝入到這些寄存器中的值是有效值。通常不需要將輸入和輸出寄存器列爲 clobbered,因爲 GCC 知道 "asm" 使用它們(因爲它們被明確指定爲約束)。不過,如果指令使用任何其它的寄存器,無論是明確的還是隱含的(寄存器不在輸入約束列表中出現,也不在輸出約束列表中出現),寄存器都必須被指定爲修飾列表。修飾寄存器列在第三個冒號之後,其名稱被指定爲字符串。

    至於關鍵字,如果指令以某些不可預知且不明確的方式修改了內存,則可能將 "memory" 關鍵字添加到修飾寄存器列表中。這樣就告訴 GCC 不要在不同指令之間將內存值高速緩存在寄存器中。



[目錄]



操作數約束
    前面提到過,"asm" 中的每個操作數都應該由操作數約束字符串描述,後面跟用括弧括起的 C 表達式。操作數約束主要是確定指令中操作數的尋址方式。約束也可以指定:

    ·是否允許操作數位於寄存器中,以及它可以包括在哪些種類的寄存器中
    ·操作數是否可以是內存引用,以及在這種情況下使用哪些種類的地址
    ·操作數是否可以是立即數

約束還要求兩個操作數匹配。

常用約束

    在可用的操作數約束中,只有一小部分是常用的;下面列出了這些約束以及簡要描述。有關操作數約束的完整列表,請參考 GCC 和 GAS 手冊。

寄存器操作數約束 (r)
    使用這種約束指定操作數時,它們存儲在通用寄存器中。請看下例:
           asm ("movl %%cr3, %0/n" :"=r"(cr3val));
    這裏,變量 cr3val 保存在寄存器中,%cr3 的值複製到寄存器上,cr3val 的值從該寄存器更新到內存中。指定 "r" 約束時,GCC 可以將變量 cr3val 保存在任何可用的 GPR 中。要指定寄存器,必須通過使用特定的寄存器約束直接指定寄存器名。

a   %eax
b   %ebx
c   %ecx
d   %edx
S   %esi
D   %edi

內存操作數約束 (m)
    當操作數位於內存中時,任何對它們執行的操作都將在內存位置中直接發生,這與寄存器約束正好相反,後者先將值存儲在要修改的寄存器中,然後將它寫回內存位置中。但寄存器約束通常只在對於指令來說它們是絕對必需的,或者它們可以大大提高進程速度時使用。當需要在 "asm" 內部更新 C 變量,而您又確實不希望使用寄存器來保存其值時,使用內存約束最爲有效。例如,idtr 的值存儲在內存位置 loc 中:
        ("sidt %0/n" : :"m"(loc));

匹配(數字)約束
    在某些情況下,一個變量既要充當輸入操作數,也要充當輸出操作數。可以通過使用匹配約束在 "asm" 中指定這種情況。

        asm ("incl %0" :"=a"(var):"0"(var));

    在匹配約束的示例中,寄存器 %eax 既用作輸入變量,也用作輸出變量。將 var 輸入讀取到 %eax,增加後將更新的 %eax 再次存儲在 var 中。這裏的 "0" 指定第 0 個輸出變量相同的約束。即,它指定 var 的輸出實例只應該存儲在 %eax 中。該約束可以用於以下情況:

   ·輸入從變量中讀取,或者變量被修改後,修改寫回到同一變量中
   ·不需要將輸入操作數和輸出操作數的實例分開
   ·使用匹配約束最重要的意義在於它們可以導致有效地使用可用寄存器。


[目錄]



示例
一般內聯彙編用法示例
    以下示例通過各種不同的操作數約束說明了用法。有如此多的約束以至於無法將它們一一列出,這裏只列出了最經常使用的那些約束類型。


[目錄]



寄存器約束
"asm" 和寄存器約束 "r"
    讓我們先看一下使用寄存器約束 r 的 "asm"。我們的示例顯示了 GCC 如何分配寄存器,以及它如何更新輸出變量的值。

int main(void)
{
    int x = 10, y;
    asm ("movl %1, %%eax;
         "movl %%eax, %0;"
         :"=r"(y)  /* y is output operand */
         :"r"(x)       /* x is input operand */
         :"%eax"); /* %eax is clobbered register */
}

    在該例中,x 的值複製爲 "asm" 中的 y。x 和 y 都通過存儲在寄存器中傳遞給 "asm"。爲該例生成的彙編代碼如下:

main:
pushl %ebp
movl %esp,%ebp
subl $8,%esp
movl $10,-4(%ebp)
movl -4(%ebp),%edx  /* x=10 is stored in %edx */
#APP    /* asm starts here */
movl %edx, %eax     /* x is moved to %eax */
movl %eax, %edx     /* y is allocated in edx and updated */
#NO_APP /* asm ends here */
movl %edx,-8(%ebp)  /* value of y in stack is updated with the value in %edx */

    當使用 "r" 約束時,GCC 在這裏可以自由分配任何寄存器。在我們的示例中,它選擇 %edx 來存儲 x。在讀取了 %edx 中 x 的值後,它爲 y 也分配了相同的寄存器。

    因爲 y 是在輸出操作數部分中指定的,所以 %edx 中更新的值存儲在 -8(%ebp),堆棧上 y 的位置中。如果 y 是在輸入部分中指定的,那麼即使它在 y 的臨時寄存器存儲值 (%edx) 中被更新,堆棧上 y 的值也不會更新。

    因爲 %eax 是在修飾列表中指定的,GCC 不在任何其它地方使用它來存儲數據。

    輸入 x 和輸出 y 都分配在同一個 %edx 寄存器中,假設輸入在輸出產生之前被消耗。請注意,如果您有許多指令,就不是這種情況了。要確保輸入和輸出分配到不同的寄存器中,可以指定 & 約束脩飾符。下面是添加了約束脩飾符的示例。

int main(void)
{
    int x = 10, y;
    asm ("movl %1, %%eax;
         "movl %%eax, %0;"
        :"=&r"(y) /* y is output operand, note the & constraint modifier. */
        :"r"(x)       /* x is input operand */
        :"%eax"); /* %eax is clobbered register */
}

    以下是爲該示例生成的彙編代碼,從中可以明顯地看出 x 和 y 存儲在 "asm" 中不同的寄存器中。

main:
pushl %ebp
movl %esp,%ebp
subl $8,%esp
movl $10,-4(%ebp)
movl -4(%ebp),%ecx  /* x, the input is in %ecx */
#APP
    movl %ecx, %eax
    movl %eax, %edx     /* y, the output is in %edx */
#NO_APP
movl %edx,-8(%ebp)

特定寄存器約束的使用
    現在讓我們看一下如何將個別寄存器作爲操作數的約束指定。在下面的示例中,cpuid 指令採用 %eax 寄存器中的輸入,然後在四個寄存器中給出輸出:%eax、%ebx、%ecx、%edx。對 cpuid 的輸入(變量 "op")傳遞到 "asm" 的 eax 寄存器中,因爲 cpuid 希望它這樣做。在輸出中使用 a、b、c 和 d 約束,分別收集四個寄存器中的值。

        asm ("cpuid"
              : "=a" (_eax),
              "=b" (_ebx),
              "=c" (_ecx),
              "=d" (_edx)
              : "a" (op));

    在下面可以看到爲它生成的彙編代碼(假設 _eax、_ebx 等... 變量都存儲在堆棧上):

movl -20(%ebp),%eax /* store 'op' in %eax -- input */
#APP
cpuid
#NO_APP
movl %eax,-4(%ebp)  /* store %eax in _eax -- output */
movl %ebx,-8(%ebp)  /* store other registers in
movl %ecx,-12(%ebp)
respective output variables */
movl %edx,-16(%ebp)

strcpy 函數可以通過以下方式使用 "S" 和 "D" 約束來實現:

        asm ("cld/n
              rep/n
              movsb"
              : /* no input */
              :"S"(src), "D"(dst), "c"(count));

    通過使用 "S" 約束將源指針 src 放入 %esi 中,使用 "D" 約束將目的指針 dst 放入 %edi 中。因爲 rep 前綴需要 count 值,所以將它放入 %ecx 中。

    在下面可以看到另一個約束,它使用兩個寄存器 %eax 和 %edx 將兩個 32 位的值合併在一起,然後生成一個64 位的值:

#define rdtscll(val) /
__asm__ __volatile__ ("rdtsc" : "=A" (val))
The generated assembly looks like this (if val has a 64 bit memory space).
#APP
rdtsc
#NO_APP
movl %eax,-8(%ebp)  /* As a result of A constraint
movl %edx,-4(%ebp)
%eax and %edx serve as outputs */
Note here that the values in %edx:%eax serve as 64 bit output.



[目錄]



匹配約束
使用匹配約束
    在下面將看到系統調用的代碼,它有四個參數:

#define _syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4) /
type name (type1 arg1, type2 arg2, type3 arg3, type4 arg4) /
{ /
long __res; /
__asm__ volatile ("int $0x80" /
: "=a" (__res) /
: "0" (__NR_##name),"b" ((long)(arg1)),"c" ((long)(arg2)), /
"d" ((long)(arg3)),"S" ((long)(arg4))); /
__syscall_return(type,__res); /
}

    在上例中,通過使用 b、c、d 和 S 約束將系統調用的四個自變量放入 %ebx、%ecx、%edx 和 %esi 中。請注意,在輸出中使用了 "=a" 約束,這樣,位於 %eax 中的系統調用的返回值就被放入變量 __res 中。通過將匹配約束 "0" 用作輸入部分中第一個操作數約束,syscall 號 __NR_##name 被放入 %eax 中,並用作對系統調用的輸入。這樣,這裏的 %eax 既可以用作輸入寄存器,又可以用作輸出寄存器。沒有其它寄存器用於這個目的。另請注意,輸入(syscall 號)在產生輸出(syscall 的返回值)之前被消耗(使用)。



[目錄]



內存操作數約束
內存操作數約束的使用
    請考慮下面的原子遞減操作:

        __asm__ __volatile__(
                "lock; decl %0"
                :"=m" (counter)
                :"m" (counter));

    爲它生成的彙編類似於:

#APP
    lock
    decl -24(%ebp) /* counter is modified on its memory location */
#NO_APP.

    您可能考慮在這裏爲 counter 使用寄存器約束。如果這樣做,counter 的值必須先複製到寄存器,遞減,然後對其內存更新。但這樣您會無法理解鎖定和原子性的全部意圖,這些明確顯示了使用內存約束的必要性。



[目錄]



修飾寄存器
使用修飾寄存器
    請考慮內存拷貝的基本實現。

       asm ("movl $count, %%ecx;
            up: lodsl;
            stosl;
            loop up;"
            :           /* no output */
            :"S"(src), "D"(dst) /* input */
            :"%ecx", "%eax" );  /* clobbered list */

    當 lodsl 修改 %eax 時,lodsl 和 stosl 指令隱含地使用它。%ecx 寄存器明確裝入 count。但 GCC 在我們通知它以前是不知道這些的,我們是通過將 %eax 和 %ecx 包括在修飾寄存器集中來通知 GCC 的。在完成這一步之前,GCC 假設 %eax 和 %ecx 是自由的,它可能決定將它們用作存儲其它的數據。請注意,%esi 和 %edi 由 "asm" 使用,它們不在修飾列表中。這是因爲已經聲明 "asm" 將在輸入操作數列表中使用它們。這裏最低限度是,如果在 "asm" 內部使用寄存器(無論是明確還是隱含地),既不出現在輸入操作數列表中,也不出現在輸出操作數列表中,必須將它列爲修飾寄存器。



[目錄]



不同的CPU下最佳編譯參數
    gcc在不同的體系機構/CPU下編譯效果有不同,需要使用不同的編譯參數達到最佳效果。


一、1.2版(gcc 2.9.x版)

-pipe -fomit-frame-pointer"
CXXFLAGS="-march=i486 -O3 -pipe -fomit-frame-pointer"

Pentium, Pentium MMX+, Celeron (Mendocino) (Intel)
CHOST="i586-pc-linux-gnu"
CFLAGS="-march=pentium -O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-march=pentium -O3 -pipe -fomit-frame-pointer"

Pentium Pro/II/III/4, Celeron (Coppermine), Celeron (Willamette?) (Intel)
CHOST="i686-pc-linux-gnu"
CFLAGS="-march=i686 -O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-march=i686 -O3 -pipe -fomit-frame-pointer"

Eden C3/Ezra (Via)
CHOST="i586-pc-linux-gnu"
CFLAGS="-march=i586 -O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-march=i586 -O3 -pipe -fomit-frame-pointer"

Quote : I did the original gentoo install using 1.2, with gcc 2.95 using -march=i586. i686 won't work.

K6 or beyond (AMD)
CHOST="i586-pc-linux-gnu"
CFLAGS="-march=k6 -O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-march=k6 -O3 -pipe -fomit-frame-pointer"

(A Duron will report "Athlon" in its /proc/cpuinfo)

Athlon (AMD)
CHOST="i686-pc-linux-gnu"
CFLAGS="-march=k6 -O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-march=k6 -O3 -pipe -fomit-frame-pointer"

For the following, i don't know of any flag that enhance performances..., do you ?

PowerPC
CHOST="powerpc-unknown-linux-gnu"
CFLAGS="-O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-O3 -pipe -fomit-frame-pointer"

Sparc
CHOST="sparc-unknown-linux-gnu"
CFLAGS="-O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-O3 -pipe -fomit-frame-pointer"

Sparc 64
CHOST="sparc64-unknown-linux-gnu"
CFLAGS="-O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-O3 -pipe -fomit-frame-pointer"


二、1.4版(gcc 3.x版):


i386 (Intel), do you really want to install gentoo on that ?
CHOST="i386-pc-linux-gnu"
CFLAGS="-march=i386 -O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-march=i386 -O3 -pipe -fomit-frame-pointer"

i486 (Intel), do you really want to install gentoo on that ?
CHOST="i486-pc-linux-gnu"
CFLAGS="-march=i486 -O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-march=i486 -O3 -pipe -fomit-frame-pointer"

Pentium 1 (Intel)
CHOST="i586-pc-linux-gnu"
CFLAGS="-march=pentium -O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-march=pentium -O3 -pipe -fomit-frame-pointer"

Pentium MMX (Intel)
CHOST="i586-pc-linux-gnu"
CFLAGS="-march=pentium-mmx -O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-march=pentium-mmx -O3 -pipe -fomit-frame-pointer"

Pentium PRO (Intel)
CHOST="i686-pc-linux-gnu"
CFLAGS="-march=pentiumpro -O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-march=pentiumpro -O3 -pipe -fomit-frame-pointer"

Pentium II (Intel)
CHOST="i686-pc-linux-gnu"
CFLAGS="-march=pentium2 -O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-march=pentium2 -O3 -pipe -fomit-frame-pointer"

Celeron (Mendocino), aka Celeron1 (Intel)
CHOST="i686-pc-linux-gnu"
CFLAGS="-march=pentium2 -O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-march=pentium2 -O3 -pipe -fomit-frame-pointer"

Pentium III (Intel)
CHOST="i686-pc-linux-gnu"
CFLAGS="-march=pentium3 -O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-march=pentium3 -O3 -pipe -fomit-frame-pointer"

Celeron (Coppermine) aka Celeron2 (Intel)
CHOST="i686-pc-linux-gnu"
CFLAGS="-march=pentium3 -O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-march=pentium3 -O3 -pipe -fomit-frame-pointer"

Celeron (Willamette?) (Intel)
CHOST="i686-pc-linux-gnu"
CFLAGS="-march=pentium4 -O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-march=pentium4 -O3 -pipe -fomit-frame-pointer"

Pentium 4 (Intel)
CHOST="i686-pc-linux-gnu"
CFLAGS="-march=pentium4 -O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-march=pentium4 -O3 -pipe -fomit-frame-pointer"

Eden C3/Ezra (Via)
CHOST="i586-pc-linux-gnu"
CFLAGS="-march=i586 -m3dnow -O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-march=i586 -m3dnow -O3 -pipe -fomit-frame-pointer"

quote : the ezra doesn't have any special instructions that you could optimize for, just consider is a K6-3...basically
a p2 with 3dnow

K6 (AMD)
CHOST="i586-pc-linux-gnu"
CFLAGS="-march=k6 -O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-march=k6 -O3 -pipe -fomit-frame-pointer"

K6-2 (AMD)
CHOST="i586-pc-linux-gnu"
CFLAGS="-march=k6-2 -O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-march=k6-2 -O3 -pipe -fomit-frame-pointer"

K6-3 (AMD)
CHOST="i586-pc-linux-gnu"
CFLAGS="-march=k6-3 -O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-march=k6-3 -O3 -pipe -fomit-frame-pointer"

Athlon (AMD)
CHOST="i686-pc-linux-gnu"
CFLAGS="-march=athlon -O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-march=athlon -O3 -pipe -fomit-frame-pointer"

Athlon-tbird, aka K7 (AMD)
CHOST="i686-pc-linux-gnu"
CFLAGS="-march=athlon-tbird -O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-march=athlon-tbird -O3 -pipe -fomit-frame-pointer"

Athlon-tbird XP (AMD)
CHOST="i686-pc-linux-gnu"
CFLAGS="-march=athlon-xp -O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-march=athlon-xp -O3 -pipe -fomit-frame-pointer"

Athlon 4(AMD)
CHOST="i686-pc-linux-gnu"
CFLAGS="-march=athlon-4 -O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-march=athlon-4 -O3 -pipe -fomit-frame-pointer"

Athlon XP (AMD)
CHOST="i686-pc-linux-gnu"
CFLAGS="-march=athlon-xp -O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-march=athlon-xp -O3 -pipe -fomit-frame-pointer"

Athlon MP (AMD)
CHOST="i686-pc-linux-gnu"
CFLAGS="-march=athlon-mp -O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-march=athlon-mp -O3 -pipe -fomit-frame-pointer"

603 (PowerPC)
CHOST="powerpc-unknown-linux-gnu"
CFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char"
CXXFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char"

603e (PowerPC)
CHOST="powerpc-unknown-linux-gnu"
CFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char"
CXXFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char"

604 (PowerPC)
CHOST="powerpc-unknown-linux-gnu"
CFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char"
CXXFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char"

604e (PowerPC)
CHOST="powerpc-unknown-linux-gnu"
CFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char"
CXXFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char"

750 aka as G3 (PowerPC)
CHOST="powerpc-unknown-linux-gnu"
CFLAGS="-mcpu=750 -O3 -pipe -fomit-frame-pointer
-fsigned-char"
CXXFLAGS="-mcpu=750 -O3 -pipe -fomit-frame-pointer
-fsigned-char"

Note: do not use -march=

7400, aka G4 (PowerPC)
CHOST="powerpc-unknown-linux-gnu"
CFLAGS="-mcpu=7400 -O3 -pipe -fomit-frame-pointer
-fsigned-char -maltivec"
CXXFLAGS="-mcpu=7400 -O3 -pipe -fomit-frame-pointer
-fsigned-char -maltivec"

Note: do not use -march=

7450, aka G4 second generation (PowerPC)
CHOST="powerpc-unknown-linux-gnu"
CFLAGS="-mcpu=7450 -O3 -pipe -fomit-frame-pointer
-fsigned-char -maltivec"
CXXFLAGS="-mcpu=7450 -O3 -pipe -fomit-frame-pointer
-fsigned-char -maltivec"

Note: do not use -march=

PowerPC (If you don't know which one)
CHOST="powerpc-unknown-linux-gnu"
CFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char"
CXXFLAGS="-O3 -pipe -fomit-frame-pointer -fsigned-char"

Sparc
CHOST="sparc-unknown-linux-gnu"
CFLAGS="-O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-O3 -pipe -fomit-frame-pointer"

Sparc 64
CHOST="sparc64-unknown-linux-gnu"
CFLAGS="-O3 -pipe -fomit-frame-pointer"
CXXFLAGS="-O3 -pipe -fomit-frame-pointer"


[目錄]



代碼維護
[目錄]



簡單cvs
簡單CVS
一、所要解決的問題
    由於軟件項目越來越大,也增加了軟件項目管理的難度。在開發組中,每個成員都要保留一個副本,在開發中非常容易引起衝突。CVS 就是爲了解決這個問題的。

    a、修改同步,防止一名開發人員的修改覆蓋其他人的成果。(check out、read only)

    b、維護不同的版本。(按 version 查找)

    c、可查找歷史記錄。防止 bug 的再引入。(diff)

    CVS 爲了解決這個問題,採用的方式是:

    當開發人員對源代碼進行修改時,修改的內容被登記(check in)到了 CVS 倉庫(repository)中。倉庫中保存了代碼的主控副本,以及歷次修改的歷史信息。它不保存文件的每個版本,而只是簡單的記錄發生在每個版本間的不同,節省磁盤空間。它能做到:

    a、使開發人員的目錄和倉庫保持一致。可以把自己的修改提交(commit)給倉庫,讓倉庫更新自己。

    b、允許代碼派生。可以進行測試,如果失敗,可以消除所做的修改,維持原結果。

    c、檢索任何一個版本。

二、使用 CVS:

    a、建立倉庫:設置並 export CVSROOT 變量,並設置倉庫目錄。比如創建 /home/cvsroot 目錄,併合理設置權限,在 .bash_profile 中加入:export CVSROOT=$HOME/cvsroot;運行 cvs init;設置讓用戶輸入日誌信息的默認的編輯器:export EDITOR=vim。如果使用的是網絡,則 CVSROOT 變量的形式是: export CVSROOT=:exit:user@server:/path,比如:export CVSROOT=:exit:david@power/home/projects/repository

    b、導入文件或目錄:cvs import filename_or_directory vender_tag release_tag;這三個參數的含義是:在倉庫中這些導入的文件所在的目錄、供應商標記、發行標記。比如 cvs import step1 david start。導入時的 N 表示所導入的文件都是新文件。

    c、使用時導出文件,用:cvs checkout directory_name;參數含義是倉庫中所在的目錄。建立新的目錄,而不是獲取他人的改動

    d、對文件修改後,保存修改到倉庫:cvs commit。

    e、如果要獲得他人的修改,使用 cvs update,U 表示本地的一個文件已經被更新。如果已經對文件進行了修改,而此是他人已經修改了該文件並提交,cvs 將告訴用戶發生衝突和衝突的位置。

    f、添加一個文件:cvs add filename; cvs commit。

    g、刪除一個文件:先在本地刪除,然後使用 cvs remove file_name; cvs commit。

三、使用標記

    可以使用標記記錄某個時刻文件的內容,這在製作發行版本的過程中非常有用:

    cvs tag release1.0

    改動後發現不正常,重新獲得這個版本,則使用:cvs checkout -r release1.0

四、測試性代碼:

    當其中一個開發者對代碼進行改進,但未來結果不能確定時,可以使用 cvs 產生出一個代碼的分之,這並不改變主控代碼:cvs tag -b for_test。導出該代碼的命令是:cvs checkout -r for_test example。由於在已有的目錄樹中不會使用該分之,因此必須重新建立目錄樹。如果測試成功,則要求將主控代碼和測試代碼合併,則先導出主控代碼,然後合併:

    cvs checkout;cvs update -jfor_test;cvs commit。



[目錄]



automake
    通常情況下,在寫完自己代碼後,使用 make 命令進行編譯。make 命令其實什麼也不做,知識讀取一個叫 Makefile 的文件,從中獲得 dependence and rule,然後調用 gcc 進行編譯。但是 Makefile 比較複雜,變化技巧也比較多。對於一個大的工程項目來說,如果沒有一個統一的風格,在工程延續的時候改動 Makefile 會很麻煩,也容易出錯誤。所以這時就有了使用 automake 的需求。使用 automake,只需要掌握一點點規則,定義一些變量,就能自動生成 Makefile。而這些 Makefile 有着統一的格式和結構,便於修改。下面就如何使用 automake 舉出一個實際的例子。

2.1 使用 automake 的前提條件


    在使用 automake 前,請先確認在系統中安裝瞭如下軟件:

    GNU Automake

    GNU Autoconf

    GNU m4

    perl

    GNU Libtool(如果需要產生 shared library)如果沒有的話,請在發行版中找相應的 rpm 包。

2.2 製作 configure 腳本

autoconf 是用來生成自動配置軟件源代碼腳本(configure)的工具。configure 腳本獨立於 autoconf 運行,而且在運行的過程中,不需要用戶的干預,通常不需要附帶參數。它是用來檢驗軟件必須的參數的。autoconf 從一個列舉編譯軟件時所需要各種參數的模板文件中創建 configure。autoconf 需要 GNU m4 來生成該腳本。

由 autoconf 生成的腳本一般起名爲 configure。當運行時,configure 創建了多個文件,並對這些文件中的配置參數賦予適當的值。由 configure 創建生成的文件有:

    1。一個或多個 Makefile,在軟件源代碼的每個目錄中都生成一個 Makefile。

    2。還可選的生成 C 頭文件——configurable,包含了各種 #define 聲明。

    3。一個名爲 config.status 的腳本,當運行時,重新生成上面的文件。

    4。一個名爲 config.cache 的腳本,保存運行檢測的結果。

    5。一個名爲 config.log 的文件,保存有編譯器生成的信息,用於調試 configure。

爲了讓 autoconf 生成 configure 腳本,需要以 configure.in 爲參數調用 autoconf。如果要檢測自己的各種參數,以作爲對 autoconf 的補充,則需要寫 aclocal.m4 和 acsite.m4 的文件。如果要使用 C 頭文件,需要寫 acconfig.h,並且將 autoconf 生成的 config.h.in 同軟件一起發行。

your source files --> [autoscan*] --> [configure.scan] --> configure.in


configure.in --. .------> autoconf* -----> configure

                   +---+

[aclocal.m4] --+   `---.

[acsite.m4] ---'       |

                      +--> [autoheader*] -> [config.h.in]

[acconfig.h] ----.     |

                 +-----'

[config.h.top] --+

[config.h.bot] --'


Makefile.in -------------------------------> Makefile.in

Files used in configuring a software package:

                       .-------------> config.cache

configure* ------------+-------------> config.log

                       |

[config.h.in] -.       v            .-> [config.h] -.

               +--> config.status* -+               +--> make*

Makefile.in ---'                    `-> Makefile ---'


編輯 configure.in 文件:

configure.in 文件中包含了對 autoconf 宏的調用,這些宏是用來檢測軟件所必須的各項參數的。爲了能夠得到 configure.in 文件,需要使用 autoscan。configure.in 文件中,在進行各項檢測前,必須在最開始調用 AC_INIT,在最後調用 AC_OUTPUT。另外有些宏由於檢測的關係是和在文件中的位置相關的。最好每一個宏佔用一行。


使用 autoscan 創建 configure.in 文件

可以將目錄做爲參數調用 autoscan,如果不使用參數的化,則認爲是當前目錄。autoscan 將檢查指定目錄中的源文件,並創建 configure.scan 文件。在將 configure.scan 改名爲 configure.in 文件前,需要手工改動它以進行調整。


使用 autoconf 創建 configure 腳本

不帶任何參數的運行 autoconf。autoconf 將使用 m4 宏處理器和 autoconf 宏,來處理處理 configure.in 中的宏。


configure.in 中的宏:

    AC_INIT(在源代碼中唯一的一個文件):configure 將檢查該文件是否存在,並檢查包含它的目錄是否存在。

    AC_OUTPUT(文件):指定創建的輸出文件。在 configure.in 文件中調用一次。文件名間用空格分開。比如:AC_OUTPUT(Makefile:templates/top.mk lib/Makefile:templates/lib.mk)

在 configure.in 中,有一些被 autoconf 宏預先定義的變量,重要的有如下幾個:

    bindir:安裝可執行文件的目錄。

    includedir:C 頭文件目錄。

    infodir:info 頁安裝目錄。

    mandir:安裝手冊頁的目錄。

    sbindir:爲管理員運行該該程序提供的安裝路徑。

    srcdir:爲 Makefile 提供的源代碼路徑。

    top_srcdir:源代碼的最上層目錄。

    LIBS:給連接程序的 -l 選項

    LDFLAGS:給連接程序的 stripping(-s)和其他一些選項。

    DEFS:給 C 編譯器的 -D 選項。

    CFLAGS:給 C 編譯器的 debug 和優化選項。當調用了 AC_PROG_CC 纔有效。

    CPPFLAGS:頭文件搜索路徑(-I)和給 C 預處理器和編譯器的其他選項。

    CXXFLAGS:給 C++ 編譯器的 debug 和優化選項。當調用了 AC_PROG_CXX 纔有效。

如果在同一個目錄下編譯多個程序的話,使用 AC_CONFIG_SUBDIRS 宏,它的語法是:

    AC_CONFIG_SUBDIRS(DIR....):

其他重要的宏:

    AC_PROG_CC:選擇 C 編譯器。如果在環境中不設置 CC 的話,則檢測 gcc。

    AC_PROG_CXX:選擇 C++ 編譯器。

參考文獻:

    Autoconf.htm
2.3 使用 automake
一般操作

    Automake 工作時,讀取一個叫'Makefile.am'的文件,並生成一個'Makefile.in'文件。Makefile.am中定義的宏和目標,會指導automake生成指定的代碼。例如,宏'bin_PROGRAMS'將導致編譯和連接的目標被生成。

    Makefile.am中包含的目標和定義的宏被拷貝到生成的文件中去,這允許你添加任意代碼到生成的Makefile.in文件中去。例如,使一個Automake發佈中包含一個非標準的dvs-dist目標,Automake的維護者用它來從它的源碼控制系統製作一個發佈。

    請注意,GNU生成的擴展名不被Automake所識別,在一個'Makefile.am'中使用這樣一個擴展名會導致錯誤。

    Automake試圖以一種聰明的方式將相鄰的目標(或變量定義)註釋重組。

    通常,Makefile.am中定義的目標會覆蓋任何由automake自動生成的有相似名字的這樣的目標。儘管這是種被支持的屬性,但最好避免這麼做,因爲有些時候,生成的規則很嚴格。

    類似的,Makefile.am中定義的變量會覆蓋任何由automake自動生成的變量定義。這一特性經常要比目標定義的覆蓋能力更常用。請注意,很多automake生成的變量只用於內部使用,在將來發布時他們的名字可能會變化。

    當測試一個變量定義時,Automake降遞歸的測試在定義中引用的變量。例如,如果Automake看到這段snippet程序中的'foo_SOURCES':

    xs = a.c b.c

    foo_SOURCES = c.c $(xs)

    它將使用文件:'a.c','b.c'和'c.c' 作爲foo_SOURCES的內容.

    Automake 也允許不被拷貝到輸出的註釋形式,所有以'##'開頭的行將被Automake 完全忽略.


深度

    Automake 支持三種目錄層次:'flat', 'shallow', 'deep'.

    flat: 所有的文件都在一個目錄中. 相應的Makefile.am中缺少SUBDIRS宏. termutils 是一個例子.

    deep: 所有的資源都在子目錄中,指定曾目錄主要包含配置信息.GNU cpio 是一個很好的例子.GNU tar.相應的最頂層Makefile.am中將包含一個SUBDIR宏,但沒有其他的宏來定義要創建的對象.

    shallow: 主資源存在於最頂層目錄,而不同的部分(典型的,庫函數)在子目錄中.Automake 就是這樣的一個包.


嚴格性

    當Automake 被GNU包維護者使用時,它的確努力去適應,但不要試圖使用所有的GNU慣例.

    目前,Automake 支持三種嚴格性標準:

    foreign:Automake 將只檢查絕對必須的東西.

    gnu:Automake 將儘可能多的檢查以適應GNU標準, 這是默認項.

    gnits:Automake 將進行檢查,以適應“尚未成文”的Gnits標準。 他們基於GNU標準,但更詳盡。除非您是Gnits標準的制定者。建議您最好避免這個選項,指導該標準正式發佈。


統一命名規範

    Automake變量一般遵循一套統一的命名規範以很容易的決定如何創建和安裝程序(和其他派生對象)。給規範還支持configure時動態決定創建規則。

    在make時,一些變量被用於決定那些對象要被創建。寫變量叫做primary variables。例如,PROGRAM變量包括一個要被編譯和連接的程序列表。

    另一個變量集用於決定被創建變量被安裝到哪裏。這些變量以相應的主變量命名,但加一個前綴,表示那些標準目錄應被用作安裝路徑。這些標準目錄的名稱規定在GNU標準中。Automake用pkglibdir, pkgincludedir 和 pkgdatadir來展開這一列表。他們和沒有pkg前綴的版本一樣,只不過有‘@PACHAGE@’擴展,PKGLIBDIR被定義爲 $(DATADIR)/@PACKAGE@.

    對每一個主變量,有一個EXTRA_前綴的變量。這個變量用於列出所有對象,至於哪些變量被創建,哪些變量不被創建則取決於configure。之所以需要這個變量,是因爲Automake必須靜態的指導要創建對象的完整列表以便生成一個‘Makefile。in’文件。

    例如,cpio 在configure時決定創建那些程序。一些程序被安裝在bindir,一些被安裝在sbindir:

    EXTRA_PROGRAMS = mt rmt

    bin_PROGRAMS = cpoi pax

    sbin_PRGRAMS = @PROGRAMS@

    定義沒有前綴的主變量是錯誤的(如:PROGRAMS)。值得注意的是,“dir”在作爲構成變量名時會被忽略。一次,我們寫成bin_PROGRAMS 而不是bindir_PROGRAMS.

    不是每一種對象都得以安裝在每個目錄下。Automake 將標記那些他認爲是錯誤的嘗試,他也能夠診斷一些明顯的目錄名拼寫錯誤。

    有時標準目錄--被Automake使用的--不過用。特別的,有時爲了清晰,將對象安裝在一些預定義的子目錄,是很有用的。Automake允許你增加安裝目錄。如果以一個變量名(如,zar)加上dir的另一個變量(如,zardir)被定義了,那麼他也是合法的。

    例如,如果HTML沒支持Automake的一下部分,你就可以用他來安裝HTML源文件:

    htmldir = $(prefix)/html

    html_DATA = automake.html

    “noinst”前綴專門指定有問題的對象不被安裝。

    “check”前綴表示有問題的對象知道make check命令被執行猜被創建。

    可用的主變量是 'PROGRAMS','LIBRARIES','LISP','SCRIPTS','DATA','HEADERS','MANS'和'TEXINFOS' 導出變量是如何命名的

    有時一個Makefile變量名有一些用戶支持的文本導出。例如程序名被重寫進Makefile宏名稱。Automake讀取這些文本,所以他不必遵循命名規則。當生成後引用時名稱中的字符除了字母,數字,下劃線奪回被轉換爲下劃線。例如,如果你的程序裏有sniff-glue,則導出變量名將會是sniff_glue_SOURCES,而不是sniff-glue_SOURCES.


一些例子


一個完整簡單的例子

    假設你寫了一個名爲zardoz的程序。

    第一步,更新你的configure.in文件以包含automake所需的命令。最簡單的辦法就是在AC_INIT後加一個AM_INIT_AUTOMAKE調用:

    AM_INIT_AUTOMAKE(zardoz, 1.0)

    如果你的程序沒有任何複雜的因素。這是最簡單的辦法。

    現在,你必須重建‘configure’文件。這樣做,你必須告訴autoconf如何找到你所用的新宏。最簡單的方式是使用aclocal程序來 生成你的‘aclocal.m4’.aclocal讓你將你的宏加進‘acincluide.m4’,所以你只需重命名並運行他


    mv aclocal.m4 acinclude.m4

    aclocal

    autoconf


    現在是爲你的zardoz寫Makefile.am的時候了。zardoz是一個用戶程序,所以逆向將他安裝在其他用戶程序安裝的目錄。zardoz還有 一些Texinfo文檔。你的configure.in腳本使用AC_REPLACE_FUNCS,所以你需要鏈接‘@LIBOBJS@’

    bin_PROGRAMS = zardoz

    zardoz_SOURCES = main.c head.c float.c vortex9.c gun.c

    zardoz_LDADD = @LIBOBJS@


    info_TEXINFOS = zardoz.texi


    現在你可以運行Automake以生成你的Makefile.in文件。


一個經典的程序

    hello 以其簡單和多面幸而聞名。着一段將顯示在Hello包Automake如何被使用。

    下面是

    dnl用 autoconf 處理它以產生一個 configure 腳本.

    AC_INIT(src/hello.c)

    AM_INIT_AUTOMAKE(hello, 1.3.11)

    AM_CONFIG_HEADER(config.h)


    dnl Set of available languages

    ALL_LINGUAS="de fr es ko nl no pl pt sl sv"

    dnl Checks for programs.

    AC_PROG_CC

    AC_ISC_POSIX

    dnl Checks for libraries.

    dnl Checks for header files.

    AC_STDC_HEADERS

    AC_HAVE_HEADERS(string.h fcntl.h sys/file.h sys/param.h)

    dnl Checks for library functions.

    AC_FUNC_ALLOCA

    dnl Check for st_blksize in struct stat

    AC_ST_BLKSIZE

    dnl internationalization macros

    AM_GNU_GETTEXT

    AC_OUTPUT([Makefile doc/Makefile intl/Makefile po/Makefile.in / src/Makefile tests/Makefile tests/hello], [chmod +x tests/hello])

    'AM_'宏由Automake(或Gettext 庫)提供;其餘的是Autoconf標準宏。

    top-level ‘Makefile.am’:

    EXTRA_DIST = BUGS ChangeLog.O

    SUBDIRS = doc intl po src tests

    --如你所見,這裏所有的工作時在子目錄中真正完成的.

    --'po' 和 'intl'目錄是用 gettextize自動生成的,這裏不做進一步討論.

    在'doc/Makefile.am'文件中我們看到:

    info_TEXINFOS = hello.texi

    hello_TEXINFOS = gpl.texi

    --這已足以創建,安裝和發佈手冊.


    這裏是'tests/Makefile.am'文件:

    TESTS = hello

    EXTRA_DIST = hello.in testdata

    --腳本'hello'被configure創建,並且是唯一的測試.make check將運行它.

    最後是,'src/Makefile.am',所有的真正的工作是在這裏完成的:

    bin_PROGRAMS = hello

    hello_SOURCES = hello.c version.c getopt.c getopt1.c getopt.h system.h

    hello_LDADD = @INTLLIBS@ @ALLOCA@

    localedir = $(datadir)/locale

    INCLUDES = -I../intl -DLOCALEDIR=/"$(localedir)/"


創建一個'Makefile.in'文件

    要爲一個包創建所有的'Makefile.in',在頂層目錄運行automake不帶參數的程序.automake將自動查找每個適當的'Makefile.am'文件,並聲稱相應的'Makefile.in'文件.請注意,automake會簡要的察看一下包的構成;它假定一個包在頂層只有一個'configure.in'文件.如果你的包有多個'configure.in'文件,那麼你必須在每一個含有'configure.in'文件的目錄運行一次'automake'程序.

    你可以隨意送給automake一個參數;'.am'會加到變量名的後面,並且作爲輸入文件的文件名.這一特性通常被用來自動重建一個'過時的''Makefile.in'文件.注意,automake必須在最頂級的目錄運行,及時僅僅爲了重新生成某個子目錄中的'Makefile.in'文件.

automake接受以下可選參數:

    -a

    --add-missing


    在某些特殊情況下,Automake需要某些公共文件存在.例如,如果'configure.in'運行 AC_CANONICAL_HOST時,就需要'config.guess'.Automake需要幾個這樣的文件,這一選項會讓automake自動的將一些缺失文件加進包中(如果可能的話).一般的,如果Automake告訴你某個文件不存在,你可以試一試這個選項.

    --amdir = dir


    在dir所制定的目錄而不是安裝目錄下尋找Automake的數據文件,經常用於調試.

    --build-dir = dir


    告訴Automake創建目錄在哪裏.這一選項只用於將'dependencies'加進一個由make dist生成的'Makefile.in'文件中.

    --cygnus


    將使得所生成的'Makefile.in'文件遵循Cygnus規則,而不是GNU或Gnits規則.

    --foreign


    設置全局嚴格性爲'gnits'.

    --gnu


    設置全局嚴格性爲'gnu'.

    --help


    幫助

    -i

    --include-deps


    將所有自動創建時的依賴信息包括在'Makefile.in'中.通常在製作發行時用到.

    --generate-deps


    創建一個'.dep_segment'文件,它合併了所有的自動生成時的依賴信息,通常用於製作發行.這在維護'SMakefile'或其他平臺上的'Makefile'(如Makefile.Dos)時非常有用.它只能與--include-deps, --srcdir-name和 --build-dir 聯合使用.注意這一選項不作其他任何處理.

    --no-force


    一般的,automake創建在'configure.in'中提及的所有'Makefile.in'文件.這一選項將導致,automake只更新那些對於與自身相關的東西過了時的'Makefile.in'文件.

    -o dir

    --output-dir = dir


    將生成的'Makefile.in'放在指定的dir目錄中.通常,'Makefile.in'放在與之相對應的'Makefile.am'所在目錄中的.在製作發佈時使用.

    --srcdir-name = dir


    告訴Automake與當前創建動作相關的源文件目錄的名字.個選項僅被用於將自動創建時的依賴信息包括在'Makefile.in'中.

    -v

    --verbose


    時Automake打印出正在讀取或創建的文件的信息.

    --version


    打印Automake的版本號.


掃描'configure.in'文件

    Automake掃描包的'configure.in'文件來決定某些關於包的信息.'configure.in'中必須定義一些變量並且需要一些autoconf 宏.Automake也會利用'configure.in'文件的信息來定製它的輸出.

    Automake還支持一些autoconf宏以使維護更爲簡單.


配置需求

    滿足Automake基本要求的最簡單方法是使用'AM_INIT_AUTOMAKE'宏.但如果你願意,你可以手工來做.


自動生成 aclocale.m4

    Automake包括很多Autoconf宏,其中有些在某些情況下游Automake使用,這些宏必須被定義在你的'aclocal.m4'中.否則,autoconf將看不到他們.

    acloal程序將根據'configure.in'自動生成'aclocal.m4'.這提供了一個方便的途徑來得到Automake提供的宏.


Automake所支持的Autoconf宏

    AM_CONFIG_HEADER

    AM_CYGWIN32

    AM_FUNC_STRTOD

    AM_FUNC_ERROR_AT_LINE

    AM_FUNC_MKTIME

    AM_FUNC_OBSTACK

    AM_C_PROTOTYPES

    AM_HEADER_TOCGWINSZ_NEEDS_SYS_IOCTL

    AM_INIT_AUTOMAKE

    AM_PATH_LISPDIR

    AM_PROG_CC_STDC

    AM_PROG_INTALL

    AM_PROG_LEX

    AM_SANITY_CHECK

    AM_SYS_POSIX_TERMIOS

    AM_TYPE_PTRDIFF_T

    AM_WITH_DMALLOC

    AM_WITH_REGEX


編寫你自己的aclocal宏

    Aclocal並沒有特定的宏機制,因此你可以用你自己的宏來擴展它.這以特性經常被用以製作做那些想讓自己的Autoconf宏被其他應用程序使用的庫.例如,gettext庫支持一個AM_GNU_GETTEXT宏,它可以被任何使用gettext庫的包所使用.當該庫被安裝後,它會安裝這個宏,這樣,aclocal就能夠找到他了.

    宏的名稱應以'.m4'結尾,這樣的文件將被安裝在'$(datadir)/aclocal'中.


最頂層'Makefile.am'

    在non-flat包中,頂層'Makefile.am'必須告訴Automake哪些子目錄將被創建.這是通過SUBDIRS變量定義的.

    SUBDIRS宏包含一個子目錄列表,以規定各種創建的過程.'Makefile'中很多目標(如,all)將不止運行所在目錄中,還要運行於所有指定的子目錄中.注意,在SUBDIRS中列出的目錄並不需要包含'Makefile.am'文件而只需要'Makefile'文件.這將允許包含位於不使用Automake的包(如,gettext)中的庫.另外,SUBDIRS之中的目錄必須是當前目錄的直接子目錄.比如,你不能在SUBDIRS中指定'src/subdir'.

    在deep型的包中,頂層'Makefile.am'文件通常非常短.例如,下面是HELLO發佈中的'Makefile.am':


    EXTRA_DIST = BUGS ChangeLog.O README-alpha

    SUBDIRS = doc intl po src tests


    如果你只想創建一個包的子集,你可以覆蓋SUBDIRS(就如同GNU inetutils的情形)在你的'Makefile.am'中包括:

    SUBDIRS = @SUBDIRS@

    讓後在你的'configure.in'中你可以指定:


    SUBDIRS = "src doc lib po"

    AC_SUBST(SUBDIRS)


    這樣做的結果是automake被欺騙了,它會在指定目錄創建包,但並不真正綁定那個列表直到運行configure.


    SUBDIRS可以包含配置的替代(如,'@DIRS@');Automake 自己並不真正檢測這一變量的內容.

    如果 SUBDIRS 被定義了,那麼你的'configure.in' 就必須包含AC_PROG_MAKE_SET.

    SUBDIRS 的使用並不侷限於頂層'Makefile.am'.Automake 可被用來構造任意深度的包.


參考文獻

    Automake.htm




[目錄]



diff
diff

  diff是生成源碼補丁的必備工具。其命令格式爲:

 diff [命令行選項] 原始文件 新文件

常用命令行選項如下:

   -r 遞歸處理目錄     -u 輸出統一格式(unified format)

   -N patch裏包含新文件   -a patch裏可以包含二進制文件

  它的輸出在stdout上,所以你可能需要把它重定向到一個文件。diff的輸出有“傳統格式”和“統一格式”之分,現在大都使用統一格式:

  傳統格式示例:

   [hahalee@builder]$ diff a.txt b.txt

   1a2

   > here we insert a new line

   3d3

   < why not this third line?

  統一格式示例:

   [hahalee@builder]$ diff -u a.txt b.txt

   --- a.txt Thu Apr 6 15:58:34 2000

   +++ b.txt Thu Apr 6 15:57:53 2000

   @@ -1,3 +1,3 @@

   This is line one

   +here we insert a new line

   and this is line two

   -why not this third line?

  通過比較可以看出,傳統格式的patch文件比較小,除了要刪除/插入的行外沒有冗餘信息。統一格式則保存了上下文(缺省是上下各三行,最少需要兩行),這樣,patch的時候可以允許行號不精確匹配的情況出現。另外,在patch文件的開頭明確地用---和+++標示出原始文件和當前文件,也方便閱讀。要選用統一格式,用 u 開關。

  通常,我們需要對整個軟件包做修改,並生成一個patch文件,下面是典型的操作過程。這裏就要用到前面介紹的幾個命令行開關了:

  tar xzvf software.tar.gz # 展開原始軟件包,其目錄爲software

  cp _a software software-orig # 做個修改前的備份

  cd software

  [修改,測試……]

  cd ..

  diff _ruNa software-orig software > software-my.patch

  現在我們就可以保存software-my.patch做爲這次修改的結果,至於原始軟件包,可以不必保存。等到下次需要再修改的時候,可以用patch命令把這個補丁打進原始包,再繼續工作。比如是在linux kernel 上做的工作,就不必每次保存幾十兆修改後的源碼了。這是好處之一,好處之二是維護方便,由於unified patch格式有一定的模糊匹配能力,能減少原軟件包升級帶來的維護工作量(見後)


patch

  patch命令跟diff配合使用,把生成的補丁應用到現有代碼上。常用命令行選項:

  patch [命令行選項] [待patch的文件[patch]]

  -pn patch level(n是數字) -b[後綴] 生成備份,缺省是.orig

爲了說明什麼是patch level,這裏看一個patch文件的頭標記。

  diff -ruNa xc.orig/config/cf/Imake.cf xc.bsd/config/cf/Imake.cf

  --- xc.orig/config/cf/Imake.cf Fri Jul 30 12:45:47 1999

  +++ xc.new/config/cf/Imake.cf Fri Jan 21 13:48:44 2000

  這個patch如果直接應用,它會去找xc.orig/config/cf目錄下的Imake.cf文件,假如你的源碼樹的根目錄是缺省的xc而不是xc.orig,除了mv xc xc.orig之外,有無簡單的方法應用此patch呢?patch level就是爲此而設:patch會把目標路徑名砍去開頭patch level個節(由/分開的部分)。在本例中,可以用下述命令:cd xc; patch _p1 < /pathname/xxx.patch 完成操作。注意,由於沒有指定patch文件,patch程序默認從stdin讀入,所以用了輸入重定向。

  如果patch成功,缺省是不建備份文件的(注:FreeBSD下的patch工具缺省是保存備份),如果你需要,可以加上 b 開關。這樣把修改前的文件以“原文件名.orig”的名字做備份。如果你喜歡其它後綴名,也可以用“b 後綴”來指定。

  如果patch失敗,patch會把成功的patch行給patch上,同時(無條件)生成備份文件和一個.rej文件。.rej文件裏是沒有成功提交的patch行,需要手工patch上去。這種情況在原碼升級的時候有可能會發生。

  關於二進制文件的說明:binary文件可以原始方式存入patch文件。diff可以生成(加-a選項),patch也可以識別。如果覺得這樣的patch文件太難看,解決方法之一是用uuencode處理該binary文件。




[目錄]



rcs
  單個文件的版本控制/管理,適合對少量文件進行版本控制,不適合小組進行項目協作開發。優點:使用簡便;缺點:功能有限。RCS常用命令有ci/co/rcsdiff。

  rcs用一個後綴爲“,v”的文件保存一文件的內容和所有修改的歷史信息,你可以隨時取出任意一個版本,用rcs保存程序就不必爲不同版本分別備份。下面是一個“,v”文件的例子:

 (太長,忽略。請看演示或自己找一個樣本)

  rcs文件裏記載了每次更新的日期、作者、還有更新說明(Log)。文件的最新版本內容放在Log之後,再後面是歷次版本與其後一版本的差別,按check in的時間做倒序排列。這麼做的原因是因爲新版本的check out機會大些,倒序排列可優化check out時間。

   ci _ check in,保存新版本

  此命令把指定文件添加到rcs歷史文件中,同時把該文件刪除。如果當前目錄下有個RCS目錄,ci會把歷史文件存在此處,否則放在當前目錄下。

   [hahalee@builder]$ mkdir RCS

   [hahalee@builder]$ ci wood.txt

   RCS/wood.txt,v <-- wood.txt

   enter description, terminated with single '.' or end of file:

   NOTE: This is NOT the log message!

   >> initial checkin #NOTE: 這裏是給本次checkin做的說明

   >> .

   initial revision: 1.1

   done

   [hahalee@builder]$ ls -l RCS/

   總共 4

   -r--r--r-- 1 hahalee hahalee 451 Apr 7 07:27 wood.txt,v

  ci也有豐富的命令行選項,比如,可以指定check in的版本號,甚至可以用字符串來做版本號,請查閱ci的manpage。

   co _ check out,取出當前(或任意)版本

常用命令行選項:

  -r[rev] 指定版本的checkout -l[rev] 指定版本,加鎖的checkout

  如不加可選的版本號,缺省是最近的版本。如果只需要一份只讀的拷貝,用-r(特殊情況,如需要一份只讀的當前拷貝,可以不要任何選項)。如需要對checkout的文件進行修改,請用-l選項。常見的操作流程是:

   ci xxx.c; co _l xxx.c; 編輯,測試; ci xxx.c …………

  在每次checkin的時候,版本號會自動在最低位上加1。爲了在文件中反映版本控制信息,rcs提供了幾個特殊的關鍵字,這裏介紹$Id$和$Log$,其它的請參考info cvs。

  $Id$代表文件的版本標識,由文件名/版本號/最後一次checkin時間/最後一次checkin的用戶這幾項組成,比如:

$Id: wood.txt,v 1.3 2000/04/07 00:06:52 hahalee Exp $

如果需要更詳細的信息,可以用$Log$,$Log$被擴展爲該文件的所有修改日期和備註,例:

    /* $Log: wood.txt,v $

    * Revision 1.2 2000/04/07 00:29:12 hahalee

    * This is my second checkin

    *

    * Revision 1.1 2000/04/07 00:28:39 hahalee

    * Initial revision

    * /

  順便介紹一下ident命令。它的功能比較簡單,就是從文件中搜索出RCS標示符並打印出來。可以用ident /usr/sbin/sendmail 來看看。不用說,如果想在最終的binary文件裏找到$Id$串,得要把它聲明到一個字符串裏去。很多程序裏這麼寫:

    #ifndef lint //這裏是爲了避免lint報告“變量未使用”

    static const char rcsid[] =

   "$Id: bin/sh.c,v 1.15 1999/08/27 23:13:43 wp Exp $"; //這是從 $Id$ 擴展                                                                                                                                                  //出來的

    #endif

   rcsdiff _ 比較revision之間的差異.運行diff命令,生成patch文件

命令行格式:rcsdiff [選項] [-r版本[-r版本]] [diff選項] 文件名

  說明:如果沒給出版本號,把上次checkin的內容同當前工作文件比較;如給出了一個版本號,就把那個版本的內容同當前工作文件比較;若給出了兩個版本號,則用第一個來跟第二個比較。由於rcsdiff調用diff命令,所有的diff選項都可用。它的輸出也是加了額外信息的diff格式內容,可以給patch使用。

  rcs裏面還有rcs,rcsclean,rlog,merge,rcsmerge我們沒有提到,有的特別簡單有的特別繁瑣且用得少。其中rcs命令可以用來對rcs文件進行各種精細的維護,最爲複雜。





[目錄]



內核重編譯常見故障
    該程序只是簡單地錄一小段純音頻數據存儲在test.wav中,用命令cat test.wav >/dev/audio就可以播放出來。因爲程序要讀寫聲卡的設備文件,所以你必須用相應的權限,或者你是root

#include <sys/soundcard.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

main()
{
  int id,fd,i,j;
  char testbuf[4096];
  id=open("/dev/audio",O_RDWR);
  //這裏使用聲卡驅動程序,linux裏當作一個文件讀寫
  fd=open("test.wav",O_RDWR);
  //一個文件

  if(id<0||fd<0){
    printf("error in open the device!/n");
    printf("id %d/t dsp%d/t seq%d/n",id,dsp,seq);
    exit(-1);
  }

  post=0;
  ioctl(id,SNDCTL_DSP_RESET,(char *)&i);
  ioctl(id,SNDCTL_DSP_SYNC,(char *)&i);
  i=1;
  ioctl(id,SNDCTL_DSP_NONBLOCK,(char *)&i);
  i=8000;
  ioctl(id,SNDCTL_DSP_SPEED,(char *)&i);
  i=1;
  ioctl(id,SNDCTL_DSP_CHANNELS,(char *)&i);
  i=8;
  ioctl(id,SNDCTL_DSP_SETFMT,(char *)&i);
  i=3;
  ioctl(id,SNDCTL_DSP_SETTRIGGER,(char *)&i);
  i=3;
  ioctl(id,SNDCTL_DSP_SETFRAGMENT,(char *)&i);
  i=1;
  ioctl(id,SNDCTL_DSP_PROFILE,(char *)&i);

  for(j=0;j<10;){
    i=read(id,testbuf,4096);
    if(i>0){
      write(fd,filebuf,i);
      j++;
    }
  }

  close(fd);
  close(id);

}

[目錄]



cvs
  CVS,顧名思義,是個可以用在小組協作環境下的源碼版本管理系統。同類的軟件有AT&T的SCCS(Source Code Control System),還有PVCS等。在OpenSource項目裏CVS用得最爲廣泛, Linux kernel不使用CVS來維護,下面我們將會參考FreeBSD的源碼管理來做示例。CVS是個相當複雜的系統,FreeBSD甚至設有專門的“CVS管理員”(CVS “Maester”)來全面負責項目的CVS repository維護。

  下面介紹與CVS相關的若干概念和術語:

    Repository:   “源碼倉庫”,CVS存放的項目源碼歷史檔案

    CVSROOT:     該環境變量指明CVS Repository存放的目錄

    Module:      模塊。就是CVSROOT下的頂級目錄名

    Vendor Branch:  分支。在一套Repository裏可以存放多個代碼分支的歷史

    Release Tag:   發行標記。對於每一個版本,可以用符號來做標記

  下面是一個CVS repository的版本衍生圖,大致描繪了FreeBSD的版本發行情況。圖中的RELENG_3和RELENG_4表示“Release Engine”,也就是Vendor Branch,每個Branch分頭髮展,等某個Branch的開發到了一定的質量水準,就做個Release Tag。比如最近的4.0-RELEASE的Release Tag是REL_4_0。

  這些不同的Branch都存放在同一個Repository Tree裏。

  CVS是個很複雜的系統,可以參考下面兩個URL獲得進一步的信息:

http://www.cyclic.com

http://www.loria.fr/~molli/cvs-index.html

(在cvs軟件包裏含有詳細的文檔,應當查閱info版本。幾個ps文件都太老了)

下面介紹CVS的基本用法。

① Import 導入/創建CVS Repository Tree

  首先建一個目錄作爲你的CVSROOT,然後用cvs init命令對其初始化(建立一系列log,config文件)。然後到工作目錄下使用cvs import命令:

   [hahalee@builder]$ mkdir /home/hahalee/CVS

   [hahalee@builder]$ export CVSROOT=/home/hahalee/CVS

   [hahalee@builder]$ cvs init

   [hahalee@builder]$ cvs import _b 0.5.0 hftpd RELENG_0 REL_0

   N hftpd/tar.h

   N hftpd/auth.h

   [blah...blah...blah...]

   N hftpd/docs/rfcs/rfc0959.txt

   N hftpd/docs/rfcs/rfc2428.txt

   No conflicts created by this import

  上述操作在$CVSROOT下生成hftpd目錄,可以看到裏面都是後綴爲“,v”的文件,這就是import進來的Repository。RELENG_0是vendor-tag,REL_0是release-tag。vendor-tag就是vendor branch tag,可以理解爲”code name”。

② Checkout 創建私有工作目錄/Export

  換一個空目錄,運行 cvs checkout modules_name 即可:

   [hahalee@builder]$ cvs checkout hftpd# hftpd是我們的module name

   cvs checkout: Updating hftpd

   U hftpd/AUTHORS

   U hftpd/COPYING

   [blah blah blah] # 省略許多

   [hahalee@builder t]$ ls -l

   總共 0

   drwxrwxr-x 5 hahalee hahalee 1253 Apr 7 20:08 hftpd

   [hahalee@builder t]$ find ./ -type d

  從最後一條命令的輸出可看到,checkout的工作目錄裏多了CVS目錄。裏面記載了CVS相關的信息,可以方便後續的cvs操作。如果純粹是爲了拷貝出最新的source tree,可以用export,此時不會建立CVS目錄。

③ Update 更新

  當你完成某一部分代碼的時候,先不忙提交,可以把別人可能做了的其他修改update過來然後統一編譯調試無誤後再提交,這是team work的準則。在checkout出來的工作目錄下(不管什麼子目錄),直接cvsup update 就可以了,當然你要先把CVSROOT環境變量設置好。

④ Commit 提交

  很簡單,cvs commit。但你必須要在checkout出來的工作目錄裏提交才行:

   [hahalee@builder]$ cvs commit

   cvs commit: Examining .

   cvs commit: Examining docs

   cvs commit: Examining docs/man

   cvs commit: Examining docs/rfcs

   cvs commit: Examining tools

   Checking in AUTHORS;

   /home/hahalee/CVS/hftpd/AUTHORS,v <-- AUTHORS

   new revision: 0.6; previous revision: 0.5

   done

  關於併發提交衝突:任何用戶可以隨意checkout他們自己的工作拷貝,commit也是不受限制的。這樣,當用戶a和b分別checkout了1.2版的c.c,然後各自對c.c做了修改,a提交了他的修改,然後,當b提交的時候,衝突就產生了。

  這時候,cvs會做以下動作:

告訴用戶b,對c.c的提交發生衝突
對用戶b當前的c.c做備份文件.#c.c.1.2
試圖合併a和b的修改,生成新的c.c
然後,用戶b應當修改c.c,去掉/合併衝突的行,並以版本1.4提交。

⑤ Diff

  可以用類似rcsdiff的方法用cvs生成patch,命令行語法也類似

   [hahalee@builder]$ cvs diff -u -r0.5 AUTHORS

   Index: AUTHORS

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

   RCS file: /home/hahalee/CVS/hftpd/AUTHORS,v

   retrieving revision 0.5

   retrieving revision 0.6

   diff -u -r0.5 -r0.6

   --- AUTHORS 2000/04/07 10:46:02 0.5

   +++ AUTHORS 2000/04/07 14:05:57 0.6

   @@ -1,3 +1,4 @@

   +ah! let me in!

   So then, who can't spell

   Develloppopotamus?

   Quite a lot of us.

  還有一個rdiff,用來生成兩個不同的release 之間的patch。

⑥ 其他操作

   cvs 的其他操作還包括有:

   admin 管理功能

   tag 對某一版本做符號標記

   release 取消checkout,刪除工作目錄(release在這裏是“釋放”的意思)

   add,remove 往repository裏添加/刪除文件

   history 查看repository操作歷史記錄

⑦ CVS 的多平臺特性以及C/S擴展

  cvs是多平臺的,開發可以在多種平臺比如,可以把linux上的CVS Repository通過samba export出來在Windows平臺上做開發。現在很多軟件包裏包含有*NIX/Windows/MacOS等多平臺支持代碼,cvs的跨平臺特性可提供最好的多平臺開發支持。

  不過,cvs的操作是直接基於文件系統的,在需要大量遠程協作的場合問題很多,遠程的NFS mount效率太差,也會有安全問題。新版本的cvs自身內建了Client/Server支持,也可以利用Unix上傳統的遠程交互手段來通訊。

  1,通過rsh(也可用ssh替換)

  2,使用cvs自帶的C/S用戶認證:pserver(缺省端口2401)

  3,使用kerberos的gserver、kserver





[目錄]



共享庫工具
strip:
nm:
size:
string:

1 創建和使用靜態庫

創建一個靜態庫是相當簡單的。通常使用 ar 程序把一些目標文件(.o)組合在一起,
成爲一個單獨的庫,然後運行 ranlib,以給庫加入一些索引信息。

2  創建和使用共享庫
特殊的編譯和連接選項
-D_REENTRANT         使得預處理器符號 _REENTRANT 被定義,這個符號激活一些宏特性。
-fPIC                選項產生位置獨立的代碼。由於庫是在運行的時候被調入,因此這個
                     選項是必需的,因爲在編譯的時候,裝入內存的地址還不知道。如果
                     不使用這個選項,庫文件可能不會正確運行。
-shared              選項告訴編譯器產生共享庫代碼。
-Wl,-soname          -Wl 告訴編譯器將後面的參數傳遞到連接器。而 -soname 指定了
                     共享庫的 soname。


    # 可以把庫文件拷貝到 /etc/ld.so.conf 中列舉出的任何目錄中,並以
root 身份運行 ldconfig;或者
    # 運行 export LD_LIBRARY_PATH='pwd',它把當前路徑加到庫搜索路徑中去。


3  使用高級共享庫特性
1> ldd 工具
ldd 用來顯示執行文件需要哪些共享庫, 共享庫裝載管理器在哪裏找到了需要的共享庫.

2> soname

共享庫的一個非常重要的,也是非常難的概念是 soname--簡寫共享目標名(short for shared object name)。這是一個爲共享庫(.so)文件而內嵌在控制數據中的名字。如前面提到的,每一個程序都有一個需要使用的庫的清單。這個清單的內容是一系列庫的 soname,如同 ldd 顯示的那樣,共享庫裝載器必須找到這個清單。

soname 的關鍵功能是它提供了兼容性的標準。當要升級系統中的一個庫時,並且新庫的 soname 和老的庫的 soname 一樣,用舊庫連接生成的程序,使用新的庫依然能正常運行。這個特性使得在 Linux 下,升級使用共享庫的程序和定位錯誤變得十分容易。

在 Linux 中,應用程序通過使用 soname,來指定所希望庫的版本。庫作者也可以通過保留或者改變 soname 來聲明,哪些版本是相互兼容的,這使得程序員擺脫了共享庫版本衝突問題的困擾。

查看/usr/local/lib 目錄,分析 MiniGUI 的共享庫文件之間的關係

3> 共享庫裝載器

當程序被調用的時候,Linux 共享庫裝載器(也被稱爲動態連接器)也自動被調用。它的作用是保證程序所需要的所有適當版本的庫都被調入內存。共享庫裝載器名字是 ld.so 或者是 ld-linux.so,這取決於 Linux libc 的版本,它必須使用一點外部交互,才能完成自己的工作。然而它接受在環境變量和配置文件中的配置信息。

文件 /etc/ld.so.conf 定義了標準系統庫的路徑。共享庫裝載器把它作爲搜索路徑。爲了改變這個設置,必須以 root 身份運行 ldconfig 工具。這將更新 /etc/ls.so.cache 文件,這個文件其實是裝載器內部使用的文件之一。

可以使用許多環境變量控制共享庫裝載器的操作(表1-4+)。

                        表 1-4+ 共享庫裝載器環境變量
變量                       含義
LD_AOUT_LIBRARY_PATH       除了不使用 a.out 二進制格式外,與 LD_LIBRARY_PATH 相同。
LD_AOUT_PRELOAD            除了不使用 a.out 二進制格式外,與 LD_PRELOAD 相同。
LD_KEEPDIR                 只適用於 a.out 庫;忽略由它們指定的目錄。
LD_LIBRARY_PATH            將其他目錄加入庫搜索路徑。它的內容應該是由冒號
                           分隔的目錄列表,與可執行文件的 PATH 變量具有相同的格式。
                           如果調用設置用戶 ID 或者進程 ID 的程序,該變量被忽略。
LD_NOWARN                  只適用於 a.out 庫;當改變版本號是,發出警告信息。
LD_PRELOAD                 首先裝入用戶定義的庫,使得它們有機會覆蓋或者重新定義標準庫。
                           使用空格分開多個入口。對於設置用戶 ID 或者進程 ID 的程序,
                           只有被標記過的庫才被首先裝入。在 /etc/ld.so.perload 中指定
                           了全局版本號,該文件不遵守這個限制。


4> 使用 dlopen

    另外一個強大的庫函數是 dlopen()。該函數將打開一個新庫,並把它裝入內存。該函數主要用來加載庫中的符號,這些符號在編譯的時候是不知道的。比如 Apache Web 服務器利用這個函數在運行過程中加載模塊,這爲它提供了額外的能力。一個配置文件控制了加載模塊的過程。這種機制使得在系統中添加或者刪除一個模塊時,都不需要重新編譯了。

    可以在自己的程序中使用 dlopen()。dlopen() 在 dlfcn.h 中定義,並在 dl 庫中實現。它需要兩個參數:一個文件名和一個標誌。文件名可以是我們學習過的庫中的 soname。標誌指明是否立刻計算庫的依賴性。如果設置爲 RTLD_NOW 的話,則立刻計算;如果設置的是 RTLD_LAZY,則在需要的時候才計算。另外,可以指定 RTLD_GLOBAL,它使得那些在以後才加載的庫可以獲得其中的符號。

    當庫被裝入後,可以把 dlopen() 返回的句柄作爲給 dlsym() 的第一個參數,以獲得符號在庫中的地址。使用這個地址,就可以獲得庫中特定函數的指針,並且調用裝載庫中的相應函數。



[目錄]



代碼優化
   Linux 是一個多用戶系統,因此對用戶的管理是系統管理的基本組成部分。安裝 Linux 的用戶很可能就是該系統的管理員,也就是權限最高的 root。通過對用戶的管理,分清了用戶之間的責、權、利,保證了系統安全。

對軟件的評價:代碼的穩定性、友好性、代碼的易讀性、統一的風格、技巧。

    1。儘量少的使用全局變量

    2。局部變量一定要初始化,特別是指針變量

    3。成員函數功能單一,不要過分追求技巧,函數體不要過長。

         {i = i + j;      {int k = i;

         j = i - j;       i=j;

         i = i = j;       j=k;

         }                }

    4。最好有頭文件

    5。關於變量名的長短問題

    6。設計函數時考慮到通用性

    7。申請內存時,一定先要釋放。注意 if 問題。

    8。對浮點數比較大小時注意不要使用 ==

    9。最好不要用 goto 語句

    10。所有成員函數要單出口單入口

    11。函數中,要先檢驗參數的合法性

    12。最好所有的函數都有返回值,表明錯誤的原因。

    13。註釋問題

    14。類型轉化一律用顯示轉換。

    15。定義宏說,參數使用括號,結果也應該括起來

        #define SUB(a,b) ((a)-(b))

        3*SUB(3,4-5);

    16。變量長度一定要用 sizeof 來求

    17。malloc 後千萬別忘 free 及使指針等於 NULL。

    18。字符串拷貝時儘量少使用 sprintf,而使用 memcpy,最後別忘加上'/0'

    19。慎重選擇編譯時的優化選項。

    20。小組開發時,注意代碼風格的統一。




[目錄]



GNU 編碼標準
GNU 編碼標準(Coding Standards)

原文在

        http://gnu.clinux.org/prep/standards.html
看過之後隨手記錄了一點兒

Linux 命令行參數處理,
getopt();
getopt_long();
getopt_long_only();

在調用過程中多使用高層的接口;
eg. readdir;

signal handling facilities 信號處理:
1. BSD: signal          the Best
        #include <signal.h>
2. POSIX: sigaction
3. USG: signal

使用臨時文件,請檢查環境變量TMPDIR
使用由它指定的目錄

編碼格式:
or, if you want to use ANSI C, format the definition like this:

static char *
concat (char *s1, char *s2)
{
  ...
}

In ANSI C, if the arguments don't fit nicely on one line, split it
like this:

int
lots_of_args (int an_integer, long a_long, short a_short,
              double a_double, float a_float)
...

Try to avoid having two operators of different precedence at the same
level of indentation. For example, don't write this:

mode = (inmode[j] == VOIDmode
        || GET_MODE_SIZE (outmode[j]) > GET_MODE_SIZE (inmode[j])
        ? outmode[j] : inmode[j]);

Instead, use extra parentheses so that the indentation shows the
nesting:

mode = ((inmode[j] == VOIDmode
         || (GET_MODE_SIZE (outmode[j]) > GET_MODE_SIZE (inmode[j])))
        ? outmode[j] : inmode[j]);

Insert extra parentheses so that Emacs will indent the code properly.
For example, the following indentation looks nice if you do it by hand,


v = rup->ru_utime.tv_sec*1000 + rup->ru_utime.tv_usec/1000
    + rup->ru_stime.tv_sec*1000 + rup->ru_stime.tv_usec/1000;

but Emacs would alter it. Adding a set of parentheses produces something
that looks equally nice, and which Emacs will preserve:

v = (rup->ru_utime.tv_sec*1000 + rup->ru_utime.tv_usec/1000
     + rup->ru_stime.tv_sec*1000 + rup->ru_stime.tv_usec/1000);

Format do-while statements like this:

do
  {
    a = foo (a);
  }
while (a > 0);

清洗的使用C語言的構造:
1.不要省略int類型的聲明;
2.-Wall
3.不要在函數內部進行extern聲明;
4.在函數中使用另外的形參命名方式;
5.不要在局部變量和參數中映射全局變量;

變量和函數的命名方法:
1.在定義全局變量和函數的時候,不要使用過於簡單的命名方法,要通過名字反映它
們的用途;
2.不要過分使用縮寫;

3.使用下劃線來分割名字中的單詞;
4.使用枚舉類型來定義constant int,而不要用#define

不同系統間的可移植性:
1.使用Autoconf來進行配置;
2.define the "feature test macro" _GNU_SOURCE when compiling your C
files.

調用系統函數:
1.不要使用sprintf的返回值;
2.vfprintf不是都提供的;
3.main要返回int;
4.不要明確的聲明系統函數;
5.如果必須定義系統函數的話,不要指明參數類型;
6.對於處理string的函數需要特別對待;

i18n,國際化:
1.要在每一個程序中使用gettext庫;
  eg. printf (gettext ("Processing file `%s'..."));

程序的文檔化:

發佈過程:


Makefile約定:




[目錄]



書籍
     "Beginning Linux Programming"

                        published by Wrox Press
                        author: Neil Matthew and Richard Stones

     "LINUX kernel Internals"

                        published by Addison Wesley

     "Advanced Programming Language in the UNIX Environment"

                        published by Addison Wesley
                        ISBN 0-201-56317-7

                        author: W. Richard Stevens

     "UNIX Network Programming"

                        published by Prentice Hall
                        ISBN 981-3026-53-7
                        author: W. Richard Stevens

     "The UNIX C Shell field guide"

                        published by Prentice-Hall
                        ISBN 0-13-937468-X 025




[目錄]




 

發佈了14 篇原創文章 · 獲贊 1 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章