LINUX下的C編程實戰之gcc/gdb/make

 

 
 
Linux下的C編程實戰之gcc/gdb/make

1 :文本編輯器

2:Gcc 編譯器的使用

3:GDB debug調試器

4:make

 

 

一:文本編輯器

在Linux平臺下,可用任意一個文本編輯工具編輯源代碼,但筆者建議使用emacs軟件,它具備語法高亮、版本控制等附帶功能

二:GCC編譯器

       GCC 是 GNU 編譯器集合(GNU Compiler Collection)的首字母縮寫詞。它可以編譯 C,C++,Objective-C,Fortran,Java 和 Ada 語言。

  GCC是Linux平臺下最重要的開發工具,它是GNU的C和C++編譯器,其基本用法爲:

gcc [options] [filenames]

  options爲編譯選項,GCC總共提供的編譯選項超過100個,但只有少數幾個會被頻繁使用,我們僅對幾個常用選項進行介紹。

     提示:編譯選項是區分大小寫的,一定要注意

  假設我們編譯一輸出“Hello World”的程序:

/* Filename:helloworld.c */
#include <stdio.h>

int main()
{
        int i=0;
        printf("helloword!/n");
        return 0;
}

 

最簡單的編譯方法是不指定任何編譯選項:

gcc helloworld.c

  它會爲目標程序生成默認的文件名a.out,我們可用-o編譯選項來爲將產生的可執行文件指定一個文件名來代替a.out。例如,將上述名爲helloworld.c的C程序編譯爲名叫helloworld的可執行文件,需要輸入如下命令:

gcc –o helloworld helloworld.c

       -Wall 開啓編譯器幾乎所有常用的警告──強烈建議你始終使用該選項

  -c選項告訴GCC僅把源代碼編譯爲目標代碼而跳過彙編和連接的步驟;

  -S (大寫)編譯選項告訴GCC 在爲 C代碼產生了彙編語言文件後停止編譯。GCC 產生的彙編語言文件的缺省擴展名是.s,上述程序運行如下命令:
   
gcc –S helloworld.c

  將生成helloworld.c的彙編代碼,使用的是AT&T彙編。

  -E選項指示編譯器僅對輸入文件進行預處理。當這個選項被使用時,預處理器的輸出被送到標準輸出(默認爲屏幕)而不是儲存在文件裏。

  -O選項告訴GCC對源代碼進行基本優化從而使得程序執行地更快;而-O2選項告訴GCC產生儘可能小和儘可能快的代碼。使用-O2選項編譯的速度比使用-O時慢,但產生的代碼執行速度會更快。

  -g選項告訴GCC產生能被GNU調試器使用的調試信息以便調試你的程序,可喜的是,在GCC裏,我們能聯用-g和-O (產生優化代碼)。

  -pg選項告訴GCC在你的程序里加入額外的代碼,執行時,產生gprof用的剖析信息以顯示你的程序的耗時情況。

   要運行該程序,輸入可執行文件的路徑如下:
          代碼:

           $ ./hello
           helloword!


          這將可執行文件載入內存,並使 CPU 開始執行其包含的指令。

          路徑 ./ 指代當前目錄,

          因此 ./hello 載入並執行當前目錄下的可執行文件 ‘hello’。

三:GDB debug調試器

  GCC用於編譯程序,而Linux的另一個GNU工具gdb則用於調試程序。gdb是一個用來調試C和C++程序的強力調試器,我們能通過它進行一系列調試工作,包括設置斷點、觀查變量、單步等。
其最常用的命令如下:

       list:列表顯示源代碼。
       run:執行當前被調試的程序
  kill:終止正在調試的程序。
  quit:終止gdb
  file:裝入想要調試的可執行文件。
  break:在代碼裏設置斷點,程序執行到這裏時掛起   
  next:執行一行源代碼但不進入函數內部。
  step:執行一行源代碼而且進入函數內部。
       watch:監視一個變量的值
  make:不退出gdb而重新產生可執行文件
  shell:不離開gdb而執行shell


         小提示:輸入命令可以只輸入頭字母代替 如 l -> list
  

下面我們來演示怎樣用GDB來調試一個求0+1+2+3+…+99的程序:

/* Filename:sum.c */
#include <stdio.h>

int main()
{
        int i, sum;
        sum = 0;
        for (i = 0; i < 100; i++)
        {
                sum += i;
        }

        printf("the sum of 1+2+...+99 = %d", sum);

        return 0;
}

 

執行如下命令編譯sum.c(加-g選項產生debug信息):

       gcc –g –o sum sum.c

  在命令行上鍵入gdb sum並按回車鍵就可以開始調試sum了,再運行run命令執行sum,屏幕上將看到如下內容:



 list命令:


  list命令用於列出源代碼,帶行號。

break命令設置斷點

      break <lineNum>

      break <function>,

  ①行斷點:根據列出的源程序,如果我們將斷點設置在第5行,只需在gdb 命令行提示符下鍵入break 5命令設置斷點

(gdb) break 5



  這個時候我們再run,程序會停止在第5行,


  ②函數入口斷點 break <function>,它在進入指定函數(function)時停住。

  相反的,clear用於清除所有的已定義的斷點,clear <function>清除設置在函數上的斷點, clear <linenum>則清除設置在指定行上的斷點。

  watch命令
   
  watch命令用於觀查變量或表達式的值,在單步執行是顯示。

       我們觀查sum變量只需要運行watch sum:

  watch <expr>爲表達式(變量)expr設置一個觀察點,一量表達式值有變化時,程序會停止執行。

  要觀查當前設置的watch,可以使用info watchpoints命令。

  next、step命令

   next、step用於單步執行,在執行的過程中,被watch變量的變化情況將實時呈現(分別顯示Old value和New value),


   next、step命令的區別在於step遇到函數調用,會跳轉到到該函數定義的開始行去執行,而next則不進入到函數內部,它把函數調用語句當作一條普通語句執行。


四:Make

  make是所有想在Linux系統上編程的用戶必須掌握的工具,對於任何稍具規模的程序,我們都會使用到make,幾乎可以說不使用make的程序不具備任何實用價值。

  在此,我們有必要解釋編譯和連接的區別。編譯器使用源碼文件來產生某種形式的目標文件(object files),在編譯過程中,外部的符號參考並沒有被解釋或替換(即外部全局變量和函數並沒有被找到)。因此,在編譯階段所報的錯誤一般都是語法錯誤。而連接器則用於連接目標文件和程序包,生成一個可執行程序。在連接階段,一個目標文件中對別的文件中的符號的參考被解釋,如果有符號不能找到,會報告連接錯誤。

  編譯和連接的一般步驟是:第一階段把源文件一個一個的編譯成目標文件,第二階段把所有的目標文件加上需要的程序包連接成一個可執行文件。這樣的過程很痛苦,我們需要使用大量的gcc命令。

  而make則使我們從大量源文件的編譯和連接工作中解放出來,綜合爲一步完成。GNU Make的主要工作是讀進一個文本文件,稱爲makefile。這個文件記錄了哪些文件(目的文件,目的文件不一定是最後的可執行程序,它可以是任何一種文件)由哪些文件(依靠文件)產生,用什麼命令來產生。Make依靠此makefile中的信息檢查磁盤上的文件,如果目的文件的創建或修改時間比它的一個依靠文件舊的話,make就執行相應的命令,以便更新目的文件。

 Make 從 makefile(默認是當前目錄下的名爲‘Makefile’的文件)中讀取項目的描述。makefile指定了一系列目標(比如可執行文件)和依賴(比如對象文件和源文件)的編譯規則,其格式如下:

目標: 依賴   

     命令

     對每一個目標,make 檢查其對應的依賴文件修改時間來確定該目標是否需要利用對應的命令重新建立。注意到,makefile 中命令行必須以單個的 TAB 字符進行縮進,不能是空格。
       GNU Make 包含許多默認的規則(參考隱含規則)來簡化 makefile 的構建。比如說,它們指定‘.o’文件可以通過編譯‘.c’文件得到,可執行文件可以通過將‘.o’鏈接到一起獲得。隱含規則通過被叫做make變量的東西所指定,比如 CC(C 語言編譯器)和 CFLAGS(C程序的編譯選項);在makefile文件中它們通過獨佔一行的 變量=值 的形式被設置。對 C++ ,其等價的變量是CXX和CXXFLAGS,而變量CPPFLAGS則是編譯預處理選項。


  假設我們寫下如下的三個文件,add.h用於聲明add函數,add.c提供兩個整數相加的函數體,而main.c中調用add函數:

/* filename:add.h */
extern int add(int i, int j);

/* filename:add.c */
int add(int i, int j)
{
    return i + j;
}

/* filename:main.c */
#include <stdio.h>
#include "add.h"

int main()
{
    int a, b;
    a = 2;
    b = 3;
    printf("the sum of a+b is %d /n", add(a , b));
    return 0;
}

 

怎樣爲上述三個文件產生makefile呢?如下:

test : main.o add.o
gcc main.o add.o -o test

main.o : main.c add.h
gcc -c main.c -o main.o

add.o : add.c add.h
gcc -c add.c -o add.o

 

上述makefile利用add.c和add.h文件執行gcc -c add.c -o add.o命令產生add.o目標代碼,利用main.c和add.h文件執行gcc -c main.c -o main.o命令產生main.o目標代碼,最後利用main.o和add.o文件(兩個模塊的目標代碼)執行gcc main.o add.o -o test命令產生可執行文件test。

  我們可在makefile中加入變量,另外。環境變量在make過程中也被解釋成make的變量。這些變量是大小寫敏感的,一般使用大寫字母。Make變量可以做很多事情,例如:

  i) 存儲一個文件名列表;
  ii) 存儲可執行文件名;
  iii) 存儲編譯器選項。

  要定義一個變量,只需要在一行的開始寫下這個變量的名字,後面跟一個=號,再跟變量的值。引用變量的方法是寫一個$符號,後面跟(變量名)。我們把前面的 makefile 利用變量重寫一遍(並假設使用-Wall -O –g編譯選項):

OBJS = main.o add.o
CC = gcc
CFLAGS = -Wall -O -g

test : $(OBJS)
$(CC) $(OBJS) -o test

main.o : main.c add.h
$(CC) $(CFLAGS) -c main.c -o main.o

add.o : add.c add.h
$(CC) $(CFLAGS) -c add.c -o add.o

 

makefile 中還可定義清除(clean)目標,可用來清除編譯過程中產生的中間文件,例如在上述makefile文件中添加下列代碼:

 

clean:
rm -f *.o

 

運行make clean時,將執行rm -f *.o命令,刪除所有編譯過程中產生的中間文件。

  不管怎麼說,自己動手編寫makefile仍然是很複雜和煩瑣的,而且很容易出錯。因此,GNU也爲我們提供了Automake和Autoconf來輔助快速自動產生makefile,讀者可以參閱相關資料。

五:鏈接外部庫

  庫是預編譯的目標文件(object files)的集合,它們可被鏈接進程序。靜態庫以後綴爲‘.a’的特殊的存檔文件(archive file)存儲。

  標準系統庫可在目錄 /usr/lib 與 /lib 中找到。比如,在類 Unix 系統中 C 語言的數學庫一般存儲爲文件 /usr/lib/libm.a。該庫中函數的原型聲明在頭文件 /usr/include/math.h 中。C 標準庫本身存儲爲 /usr/lib/libc.a,它包含 ANSI/ISO C 標準指定的函數,比如‘printf’。對每一個 C 程序來說,libc.a 都默認被鏈接。

  下面的是一個調用數學庫 libm.a 中 sin 函數的的例子,創建文件calc.c:

 

#include <math.h>
#include <stdio.h>

int main (void)
{
    double x = sin (2.0);

  printf ("The value of sin(2.0) is %f/n", x);
  return 0;
}

 

嘗試單獨從該文件生成一個可執行文件將導致一個鏈接階段的錯誤:

    $ gcc -Wall calc.c -o calc

  /tmp/ccbR6Ojm.o: In function 'main':

  /tmp/ccbR6Ojm.o(.text+0x19): undefined reference to ‘sin’

  函數 sin,未在本程序中定義也不在默認庫‘libc.a’中;除非被指定,編譯器也不會鏈接‘libm.a’

  爲使編譯器能將 sin 鏈接進主程序‘calc.c’,我們需要提供數學庫‘libm.a’。一個容易想到但比較麻煩的做法是在命令行中顯式地指定它:

  $ gcc -Wall calc.c /usr/lib/libm.a -o calc

  函數庫‘libm.a’包含所有數學函數的目標文件,比如sin,cos,exp,log及sqrt。鏈接器將搜索所有文件來找到包含 sin 的目標文件。

  一旦包含 sin 的目標文件被找到,主程序就能被鏈接,一個完整的可執行文件就可生成了:

  $ ./calc

  The value of sin(2.0) is 0.909297

  可執行文件包含主程序的機器碼以及函數庫‘libm.a’中 sin 對應的機器碼。

  爲避免在命令行中指定長長的路徑,編譯器爲鏈接函數庫提供了快捷的選項‘-l’。例如,下面的命令

  $ gcc -Wall calc.c -lm -o calc

  與我們上面指定庫全路徑‘/usr/lib/libm.a’的命令等價。

  一般來說,選項 -lNAME使鏈接器嘗試鏈接系統庫目錄中的函數庫文件 libNAME.a。一個大型的程序通常要使用很多 -l 選項來指定要鏈接的數學庫,圖形庫,網絡庫等。

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