天天看點

LINUX下的C程式設計(Emacs、GCC、Gdb、Makefile)

目錄

          • 文本編輯器
          • Gcc編譯器
          • Gdb調試器的使用
          • Makef工程管理器
文本編輯器

Linux 系統提供了一個完整的編輯器家族系列,如 Ed、Ex、Vi 和 Emacs 等。按功能它們可以分為兩大類:行編輯器(Ed、Ex)和全螢幕編輯器(Vi、Emacs)。行編輯器每次隻能對一行進行操作,使用起來很不友善。而全螢幕編輯器可以對整個螢幕進行編輯,使用者編輯的檔案直接顯示在螢幕上,進而克服了行編輯的那種不直覺的操作方式,便于使用者學習和使用,具有強大的功能。

  • vi編輯器的使用(vim)

    指令格式:

vi  檔案路徑 檔案名
           

例:

輸入指令:

vi /home/hello.c    (或者輸入vim  /home/hello.c)
           

進入界面按任意鍵進入編輯模式:

LINUX下的C程式設計(Emacs、GCC、Gdb、Makefile)

退出并儲存:

先按下Esc鍵,然後在最後一行鍵入:wq(存檔并退出);

LINUX下的C程式設計(Emacs、GCC、Gdb、Makefile)

若鍵入:q!可強制退出。

vi/vim各模式功能鍵

(1)指令行模式下

I 切換到插入模式,此時光标當于開始輸入檔案處
A 切換到插入模式,并從目前光标所在位置的下一個位置開始輸入文字
O 切換到插入模式,且從行首開始插入新的一行
[ctrl]+[b] 螢幕往“後”翻動一頁
[ctrl]+[f] 螢幕往“前”翻動一頁
[ctrl]+[u] 螢幕往“後”翻動半頁
[ctrl]+[d] 螢幕往“前”翻動半頁
0(數字 0) 光标移到本行的開頭
G  光标移動到文章的最後
nG 光标移動到第 n 行
$ 移動到光标所在行的“行尾”
n<Enter> 光标向下移動 n 行
/name 在光标之後查找一個名為 name 的字元串
?name 在光标之前查找一個名為 name 的字元串
X 删除光标所在位置的“後面”一個字元
dd 删除光标所在行
ndd 從光标所在行開始向下删除 n 行
yy  複制光标所在行
nyy  複制光标所在行開始的向下 n 行
p  将緩沖區内的字元粘貼到光标所在位置(與 yy 搭配)
U  恢複前一個動作
           

(2)底行模式常見功能鍵

:w 将編輯的檔案儲存到磁盤中
:q 退出 Vi(系統對做過修改的檔案會給出提示)
:q! 強制退出 Vi(對修改過的檔案不作儲存)
:wq 存盤後退出
:w [filename]  另存一個命為 filename 的檔案
:set nu  顯示行号,設定之後,會在每一行的前面顯示對應行号
:set nonu  取消行号顯示
           
  • Emacs文檔編輯器的使用

    可以看到vi或vim的文檔編輯器界面看起來并不那麼愉快且每次隻能對一行操作,使用起來不太友善,我們使用另外一種文檔編輯器Emacs。

    如果系統中沒有Emacs可以利用指令j進行安裝:

sudo  yum  install  emacs
           

安裝過程中若彈出提醒按y鍵即可。

啟動emacs:

emacs 檔案名
           

文檔編輯器界面如下:

LINUX下的C程式設計(Emacs、GCC、Gdb、Makefile)

(1)例:

輸入指令打開并編輯檔案:

emacs /home/hello2.c  
           

寫入以下代碼:

#include<stdio.h>
    void main()
    {
    	int a,b,c;
    	for(a=1;a<=9;a++)
    	{
    		for(b=1;b<=a;b++)
    			printf("%d*%d=%d  ",b,a,c=a*b);
    		printf("\n");
    	}
    }
           

點選save退出即可。

(2)光标的移動:滑鼠點選或移動光标,也可以使用指令(C為ctrl,M為Alt鍵)

C-f 向前移動一個字元
M-b  向後移動一個單詞
C-b  向後移動一個字元
C-a  移動到行首
C-p  移動到上一行
C-e  移動到行尾
C-n  移動到下一行
M-< (M 加“小于号”)移動光标到整個文本的開頭
           

(3)複制文本(C為ctrl,M為Alt鍵)

在 Emacs 中的複制文本包括兩步:選擇複制區域和粘貼文本。

選擇複制區域的方法是:首先在複制起始點(A)按下“C-Spase”或“[email protected](C-Shift-2)”使它成為一個表示點,再将光标移至複制結束電(B),再按下“M-w”,就可将 A 與 B 之間的文本複制到系統的緩沖區中。在使用功能鍵 C-y 将其粘貼到指定位置。

Gcc編譯器

GNU CC(簡稱為 Gcc)是 GNU 項目中符合 ANSI C 标準的編譯系統,能夠編譯用 C、C++和 Object C 等語言編寫的程式。Gcc 不僅功能強大,而且可以編譯如 C、C++、Object C、Java、Fortran、Pascal、Modula-3 和 Ada 等多種語言,而且 Gcc 又是一個交叉平台編譯器,它能夠在目前 CPU 平台上為多種不同體系結構的硬體平台開發軟體,是以尤其适合在嵌入式領域的開發編譯。本章中的示例,除非特别注明,否則均采用 Gcc 版本為 4.0.0。

  • GCC支援的字尾名解釋
    LINUX下的C程式設計(Emacs、GCC、Gdb、Makefile)
  • Gcc的使用

    指令模式:

gcc  指令選項  檔案或目錄  -o  檔案名
           

上述指令中 -o 檔案名這個指令這個操作是為了把輸出檔案輸出到另一個檔案裡變為一個可執行檔案。

檢視編譯結果(執行可執行檔案):

./  可執行檔案名
           
  • gcc選項:
-c  隻是編譯不連結,生成目标檔案“.o”
-S  隻是編譯不彙編,生成彙編代碼
-E  隻進行預編譯,不做其他處理
-g  在可執行程式中包含标準調試資訊
-o file   把輸出檔案輸出到 file 裡
-v  列印出編譯器内部編譯各過程的指令行資訊和編譯器的版本
-I dir   在頭檔案的搜尋路徑清單中添加 dir 目錄
-L dir  在庫檔案的搜尋路徑清單中添加 dir 目錄
-static  連結靜态庫
-llibrary  連接配接名為 library 的庫檔案
-ansi  支援符合 ANSI 标準的 C 程式
-pedantic  允許發出 ANSI C 标準所列的全部警告資訊
-pedantic-error  允許發出 ANSI C 标準所列的全部錯誤資訊
-w  關閉所有告警
-Wall  允許發出 Gcc 提供的所有有用的報警資訊
-werror  把所有的告警資訊轉化為錯誤資訊,并在告警發生時終止編譯過程
           
  • 例子:

    (1)

    我們先對上面提到的用vi編輯的程式進行編譯:

    LINUX下的C程式設計(Emacs、GCC、Gdb、Makefile)
    指令:
gcc /home/hello.c  -o  c
           

結果:

LINUX下的C程式設計(Emacs、GCC、Gdb、Makefile)

沒有錯誤沒有警告

執行檔案:

./c
           

結果:

LINUX下的C程式設計(Emacs、GCC、Gdb、Makefile)

(2)用emacs編輯的程式進行編譯:

程式(99乘法表):

#include<stdio.h>
    void main()
    {
    	int a,b,c;
    	for(a=1;a<=9;a++)
    	{
    		for(b=1;b<=a;b++)
    			printf("%d*%d=%d  ",b,a,c=a*b);
    		printf("\n");
    	}
    }
           

指令:

gcc /home/hello2.c -o -c2
           

執行:

./c2
           

結果:

LINUX下的C程式設計(Emacs、GCC、Gdb、Makefile)
Gdb調試器的使用

調試是所有程式員都會面臨的問題。如何提高程式員的調試效率,更好更快地定位程式中的問題進而加快程式開發的進度,是大家共同面對的。就如讀者熟知的 Windows 下的一些調試工具,如 VC 自帶的如設定斷點、單步跟蹤等,都受到了廣大使用者的贊賞。那麼,在 Linux下有什麼很好的調試工具呢?

Gdb 調試器是一款 GNU 開發組織并釋出的 UNIX/Linux 下的程式調試工具。雖然,它沒有圖形化的友好界面,但是它強大的功能也足以與微軟的 VC 工具等媲美。

下面就請跟随筆者一步步學習 Gdb 調試器。

  • gdb使用流程

    首先,打開 Linux 下的編輯器 Vi 或者 Emacs,編輯如下代碼:

/*test.c*/
#include <stdio.h>
int sum(int m);
int main()
{
int i,n=0;
sum(50);
for(i=1; i<=50; i++)
{
n += i;
}
printf("The sum of 1-50 is %d \n", n );
}
int sum(int m)
{
int i,n=0;
for(i=1; i<=m;i++)
n += i;
printf("The sum of 1-m is %d\n", n);
return 0;
}
           

在儲存退出後首先使用 Gcc 對 test.c 進行編譯,注意一定要加上選項“-g” ,這樣編譯出

的可執行代碼中才包含調試資訊,否則之後 Gdb 無法載入該可執行檔案。

gcc -g /home/test.c  -o test
           

雖然這段程式沒有錯誤,但調試完全正确的程式可以更加了解 Gdb 的使用流程。接下來

就啟動 Gdb 進行調試。注意,Gdb 進行調試的是可執行檔案,而不是如“.c”的源代碼,因

此,需要先通過 Gcc 編譯生成可執行檔案才能用 Gdb 進行調. 也就是對上述的test檔案進行調試。

執行指令:

gdb test 
           

開始調試,出現下列提示:

[[email protected] adminxu]# gdb test
GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-115.el7
Copyright (C) 2013 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/adminxu/test...done.
(gdb) 
           

可以看出,在 Gdb 的啟動畫面中指出了 Gdb 的版本号、使用的庫檔案等資訊,接下來就

進入了由“(gdb)”開頭的指令行界面了。

  • 檢視檔案

    在 Gdb 中鍵入“l”(list)就可以檢視所載入的檔案,如下所示:

l
1	#include <stdio.h>
2	int sum(int m);
3	int main()
4	{
5	int i,n=0;
6	sum(50);
7	for(i=1; i<=50; i++)
8	{
9	n += i;
10	}
(gdb) l
11	printf("The sum of 1-50 is %d \n", n );
12	}
13	int sum(int m)
14	{
15	int i,n=0;
16	for(i=1; i<=m;i++)
17	n += i;
18	printf("The sum of 1-m is %d\n", n);
19	return 0;
20	}
(gdb) 

           

鍵入l後加enter鍵。若代碼沒有顯示完,再次鍵入l。可以看出,Gdb 列出的源代碼中明确地給出了對應的行号,這樣就可以大大地友善代碼的定位。

  • 斷點的設定與使用

    (1)設定斷點

    設定斷點是調試程式中是一個非常重要的手段,它可以使程式到一定位置暫停它的

    運作。是以,程式員在該位置處可以友善地檢視變量的值、堆棧情況等,進而找出代碼

    的症結所在。

    在 Gdb 中設定斷點非常簡單,隻需在“b”後加入對應的行号即可(這是最常用的方式,另外還有其他方式設定斷點。

(gdb) b 6
Breakpoint 1 at 0x40053c: file /home/test.c, line 6.
           

要注意的是,在 Gdb 中利用行号設定斷點是指代碼運作到對應行之前将其停止,如上例中,代碼運作到第 5 行之前暫停(并沒有運作第 5 行)。

(2)檢視斷點情況

在設定完斷點之後,使用者可以鍵入“info b”來檢視設定斷點情況,在 Gdb 中可以設定

多個斷點。

(gdb) info b
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x000000000040053c in main at /home/test.c:6
           

(3)運作代碼

接下來就可運作代碼了,Gdb 預設從首行開始運作代碼,可鍵入“r”(run)即可(若想從程式中指定行開始運作,可在 r 後面加上行号)。

(gdb) r
Starting program: /home/adminxu/test 
Breakpoint 1, main () at /home/test.c:6
6	sum(50);
           

可以看到,程式運作到斷點處就停止了(斷點設定在第六行)。

(4)檢視變量值

在程式停止運作之後,程式員所要做的工作是檢視斷點處的相關變量值。在 Gdb 中隻需

鍵入“p”+變量值即可。

(gdb) p n
$1 = 0
(gdb) p i
$2 = 0
           

在斷點之前變量n和i都沒有指派,是以為0;

(5)單步運作

單步運作可以使用指令“n”(next)或“s”(step),它們之間的差別在于:若有函數調用的時候,“s”會進入該函數而“n”不會進入該函數。是以,“s”就類似于 VC 等工具中的“stepin”,“n”類似與 VC 等工具中的“step over”。

(gdb) n
The sum of 1-m is 1275
7	for(i=1; i<=50; i++)
(gdb) s
9	n += i;
           

若是在第6行,先執行指令s

(Gdb) s
sum (m=50) at test.c:16 
16 int i,n=0;
           

則會進入引用得函數sum()函數中執行,而n不會。

(6)恢複程式運作

在檢視完所需變量及堆棧情況後,就可以使用指令“c”(continue)恢複程式的正常運作

了。這時,它會把剩餘還未執行的程式執行完,并顯示剩餘程式中的執行結果。以下是之前

使用“n”指令恢複後的執行結果:

(7)退出,鍵入q即可退出gdb調試。

(gdb)c
The sum of 1-50 is 1275 
[Inferior 1 (process 6387) exited with code 031]

           
  • 工作環境相關指令
set args 運作時的參數指定運作時參數,如 set args 2
show args 檢視設定好的運作參數
path dir 設定程式的運作路徑
show paths 檢視程式的運作路徑
set enVironment var [=value] 設定環境變量
show enVironment [var] 檢視環境變量
cd dir 進入到 dir 目錄,相當于 shell 中的 cd 指令
pwd 顯示目前工作目錄
shell command 運作 shell 的 command 指令
           
  • 設定斷點與恢複指令
bnfo b 檢視所設斷點
break 行号或函數名 <條件表達式>   設定斷點
tbreak 行号或函數名 <條件表達式>   設定臨時斷點,到達後被自動删除
delete [斷點号]  删除指定斷點,其斷點号為“info b”中的第一欄。若預設斷點号則删除所有斷點
disable [斷點号]]  停止指定斷點,使用“info b”仍能檢視此斷點。同 delete 一樣,省斷點号則停止所有斷點。
enable [斷點号]  激活指定斷點,即激活被 disable 停止的斷點
condition [斷點号] <條件表達式>  修改對應斷點的條件
ignore [斷點号]<num>  在程式執行中,忽略對應斷點 num 次
step 單步恢複程式運作,且進入函數調用
next  單步恢複程式運作,但不進入函數調用
finish  運作程式,直到目前函數完成傳回
c  繼續執行函數,直到函數結束或遇到新的斷點
           
  • gdb源碼檢視相關指令
list <行号>|<函數名>  檢視指定位置代碼
file [檔案名]  加載指定檔案
forward-search 正規表達式  源代碼前向搜尋
reverse-search 正規表達式  源代碼後向搜尋
dir dir   停止路徑名
show directories  顯示定義了的源檔案搜尋路徑
info line  顯示加載到 Gdb 記憶體中的代碼
           
  • Gdb 中檢視運作資料相關指令
print 表達式|變量   檢視程式運作時對應表達式和變量的值
x <n/f/u>  檢視記憶體變量内容。其中 n 為整數表示顯示記憶體的長度,f 表示顯示的格式,u 表示從目前位址往後請求顯示的位元組數
display 表達式  設定在單步運作或其他情況中,自動顯示的對應表達式的内容
           
  • gdb使用注意

    · 在 Gcc 編譯選項中一定要加入“-g”。

    · 隻有在代碼處于“運作”或“暫停”狀态時才能檢視變量值。

    · 設定斷點後程式在指定行之前停止。

Makef工程管理器
  • 為什引入Make工程管理?

    所謂工程管理器,顧名思義,是指管理較多的檔案的。讀者可以試想一下,有一個上百個檔案的代碼構成的項目,如果其中隻有一個或少數幾個檔案進行了修改,按照之前所學的 Gcc編譯工具,就不得不把這所有的檔案重新編譯一遍,因為編譯器并不知道哪些檔案是最 近更新的,而隻知道需要包含這些檔案才能把源代碼編譯成可執行檔案,于是,程式員就不 能不再重新輸入數目如此龐大的檔案名以完成最後的編譯工作。是以,人們就希望有一個工程管 理器能夠自動識别更新了的檔案代碼,同時又不需要重複輸入冗長的指令行,這樣,Make 工程管理器也就應運而生了。

    比如,使用gcc編譯一個檔案的時候,我們隻需要一個指令即可:

gcc hello.c -o c
           

但是1000個檔案呢?

gcc hello1.c hello2.c..............-o c
           

是不是被惡心到了,而且這樣編譯所有的檔案都必須在同一個檔案夾中。

  • makefile基本結構

    需要由 make 工具建立的目标體(target),通常是目标檔案或可執行檔案;

    要建立的目标體所依賴的檔案(dependency_file);

    建立每個目标體時需要運作的指令(command)。

    首先早對應檔案目錄下建立makefile檔案。注意檔案的命名格式為makefile或Makefile,否則無法使用makefile。鍵入指令:emacs /home/test/makefile(emacs編輯)或vim /home/test/makefile(vim編輯器)。

    進入編輯模式開始編輯makefile

    格式為:

target(目标檔案): dependency_files(依賴檔案)
[TAB]command
           

例如,有兩個檔案分别為 hello.c 和 hello.h,建立的目标體為 hello.o,執行的指令為 gcc編譯指令:

gcc –c hello.c,那麼,對應的 Makefile 就可以寫為:

#The simplest example
hello.o: hello.c hello.h
gcc –c hello.c –o hello.o
           

接着就可以使用 make 了。使用 make 的格式為:make target,這樣 make 就會自動讀入Makefile(也可以是首字母小寫 makefile)并執行對應 target 的 command 語句,并會找到相

應的依賴檔案。如下所示:

[[email protected] makefile]# make hello.o
gcc –c hello.c –o hello.o
[[email protected] makefile]# ls
hello.c hello.h hello.o Makefile
           

可以看到,Makefile 執行了“hello.o”對應的指令語句,并生成了“hello.o”目标體。

  • makefile編譯原理

    我們以一個例子說明。

    現在一個檔案夾下分别編輯檔案main.c,my1.h.my2.h,my1.c,my2.c.fen,分别編輯以下代碼:

    代碼引用:https://www.cnblogs.com/missliuxin/p/3540531.html

#include "my2.h"
int main()
{
my1_print("hello my1!");
my2_print("hello my2!");
return 0;
}
           

名稱為my1.h,代碼如下:#ifndef _MY _ 1 _ H

#define _MY_1_H
void my1_print(char *print_str);
#endif
           

m2.h

#define _MY_2_H
void my2_print(char *print);
#endif
           

名稱為my1.c,代碼如下:#include “my1.h”

#include <stdio.h>
void my1_print(char *print_str)
{
printf("This is my2 print %s\n", print_str);
}
           

名稱為my2.c,代碼如下:#include “my2.h”

#include <stdio.h>
void my2_print(char *print_str)
{
printf("This is my2 print %s\n", print_str);
           

鍵入指令:

emacs /home/test/makefile
           

編輯makefile檔案:

main:main.o my1.o my2.o
	gcc main.o my1.o my2.o -o main
main.o:main.c my1.h my2.h
	gcc -c main.c
my1.o:my1.c my1.h
	gcc -c my1.c
my2.o:my2.c my2.h
	gcc -c my2.c
.PHONY:
cleanall:
	rm -f *.o main
clean:
	rm -f *.o 
           

(gcc加-c選項表示隻編譯不連結,生成目标檔案.o)

我們需要清楚以下幾點:

#第一個目标是我們的最終标,由上面的程式可知我們要的最終目标是将main.o,my1.o,my2.o連結生成一個可執行檔案。

#make的基本工作流程,我們不難發現make的工作流程其實類似于一個遞歸的算法。首先我們先根據最終目标是生成執行檔案main,那麼久需要可連結檔案main.o,my1.o,my2.o.,那麼就會向下執行尋找main.o,my1.o,m2.o,再找到最後一個檔案後,再從下到上依次執行gcc -c my2.c,産生了三個點o類型的連接配接檔案。最後連接配接生成可執行檔案main。

#.PHONY(僞目标):一個檔案隻能有一個最終目标我們才能執行到最終的操作,但是我們又想要執行其他操作,比如我們不想要make工作後産生的.o檔案,怎樣再來一個目标進行操作來進行删除。我們就需要用到僞目标。如上述程式所做的,我們可以在僞目标後加上相關操作,其中cleanall和clean的名稱并不是固定的,這樣做不會影響最終目标的執行,又增加了新的操作。

接下來驗證makefil是否建立成功

鍵入指令:

make main
           

main為最終目标名:

LINUX下的C程式設計(Emacs、GCC、Gdb、Makefile)

執行可執行檔案main:

./main
           

結果:

LINUX下的C程式設計(Emacs、GCC、Gdb、Makefile)

我們檢視一下目前目錄下的檔案:

LINUX下的C程式設計(Emacs、GCC、Gdb、Makefile)

鍵入指令執行僞目标清除檔案:

make cleanall
           
LINUX下的C程式設計(Emacs、GCC、Gdb、Makefile)
  • makefile的自定義變量

    Makefile 中的變量分為使用者自定義變量、預定義變量、自動變量及環境變量。如上例中的 OBJS 就是使用者自定義變量,自定義變量的值由使用者自行設定,而預定義變量和自動變量為通常在 Makefile 都會出現的變量,其中部分有預設值,也就是常見的設定值,當然使用者可以對其進行修改。

    (1)自定義變量的使用

    Make 中的變量使用均使用格式為:$(VAR)。(也就是dollor符加變量名)

    例:接上面makefile例子,我們進一步做簡化

    為了更加突出變量的簡化作用,我們在原來的makefile程式中加入更多的gcc選項

    原來的指令格式

main:main.o my1.o my2.o
	gcc main.o my1.o my2.o -o main
main.o:main.c my1.h my2.h
	gcc -c -g -Wall main.c
my1.o:my1.c my1.h
	gcc -c -g -Wallmy1.c
my2.o:my2.c my2.h
	gcc -c -g -Wall my2.c
.PHONY:
cleanall:
	rm -f *.o main
clean:
	rm -f *.o 
           

我們用變量名OBJS代替main.o my1.o my2.o,用cflags代表gcc的指令選項-c -g -Wall,用cc代表gcc,如下,使用表變量時要加$( )

OBJS=main.o my1.o my2.o
cc=gcc
cflags=-c -g -Wall
main:$(OBJS)
	$(cc) $(OBJS) -o main
main.o:main.c my1.h my2.h
	$(cc) $(cflags) main.c 
my1.o:my1.c my1.h
	$(cc) $(cflags) my1.c 
my2.o:my2.c my2.h
	$(cc) $(cflags) my2.c
.PHONY:
cleanall:
	rm -f *.o main
clean:
	rm -f *.o 
           

鍵入指令make main之後結果仍一樣。

  • Makefile的自動變量
$* 不包含擴充名的目标檔案名稱
$+  所有的依賴檔案,以空格分開,并以出現的先後為序,可能包含重複的依賴檔案
$<  第一個依賴檔案的名稱
$? 所有時間戳比目标檔案晚的依賴檔案,并以空格分開
[email protected] 目标檔案的完整名稱
$^  所有不重複的依賴檔案,以空格分開
$%  如果目标是歸檔成員,則該變量表示目标的歸檔成員名稱
           

我們繼續依次改寫之前的例子:

cc=gcc
cflags=-c -g -Wall
main:main.o my1.o my2.o
	$(cc) $^ -o [email protected]
main.o:main.c my1.h my2.h
	$(cc) $(cflags) $< 
my1.o:my1.c my1.h
	$(cc) $(cflags) $< 
my2.o:my2.c my2.h
	$(cc) $(cflags) $<
.PHONY:
cleanall:
	rm -f *.o main
clean:
	rm -f *.o 
           

執行後結果仍一樣。

繼續閱讀