Linux編程環境的學習一、vim
vim編輯器基本上可以分為3種模式,分別是命令模式、插入模式和底行模式,所示為Vim各種模式相互轉換的關係圖。
命令模式:控制螢幕游標的移動,進行文本的刪除、複製等文字編輯工作(不使用[Del]鍵和[Backspace]鍵)以及進入插入模式,或者回到底行模式。
插入模式:只有在插入模式下,才可以輸入文字。按[Esc]鍵可回到命令列模式。很多Vim編輯器使用者希望一開啟Vim就可以輸入內容,但這是不能成功的,因為剛開啟Vim編輯器時處於命令模式。
底行模式:儲存檔案或退出Vim,同時也可以設定編輯環境和一些編譯工作,如列出行號、尋找字串等。
示範了三種模式之間的切換。
在命令模式下,有如下常用命令:
- 插入
a //在當前游標位置的右邊添加文本
i //在當前游標位置的左邊添加文本
A //在當前行的末尾位置添加文本
I //在當前行的開始處添加文本(非Null 字元的行首)
O //在當前行的上面建立一行
- 刪除
x //刪除當前字元
nx //刪除從游標開始的n個字元
dw //刪除從游標開始到一個單詞(word)的末尾,實際是剪下
dd //刪除當前行,剪下
ndd //向下刪除當前行在內的n行,剪下
- 拷貝
yy //將當前行複製到緩衝區。
nyy //將當前行向下n行複製到緩衝區。
yw //複製從游標開始到詞尾的字元。
nyw //複製從游標開始的n個單詞。
y^ //複製從游標到行首的內容。
y$ //複製從游標到行尾的內容。
- 粘帖
p(小寫) //粘貼剪下板裡的內容在游標後。
P(大寫) //粘貼剪下板裡的內容在游標前。
- 替換
r //替換(覆蓋)當前字元
R //替換(覆蓋)當前游標位置及後面的若干文本
- 定位
h,j,k,l //上,下,左,右也可用鍵盤上的方向鍵。
n+ //向下跳n行
n- //向上跳n行
nG //跳到行號為n的行
G //跳至檔案的底部
gg // 跳至檔案的頭部
ctrl-f //下翻一頁
ctrl-b //上翻一頁
^ //游標移到行首。
$ //游標移到行尾。
- 搜尋文本
/vpser //向游標下搜尋vpser字串
?vpser //向游標上搜尋vpser字串
n //向下搜尋
N //向上搜尋
- 替換
:s/old/new //用new替換行中首次出現的old
:s/old/new/g //用new替換行中所有的old
:n,m s/old/new/g //用new替換從n到m行裡所有的old
:%s/old/new/g //用new替換當前檔案裡所有的old
替換的時候注意,加’g’表示涉及的行中所有匹配都替換,若不加’g’則替換行中匹配的第一個,%則相當於所有行。
- 撤消操作
u //撤銷上一步操作
U //撤銷對當前行的所有操作
- 合并
J //合并游標所在行及下一行為一行(依然在命令模式)
當然vim還有很多常用的命令,這需要在以後的使用中再去總結。
二、makefile
對於單個源檔案的項目我們可以在命令列通過g++命令一步一步的執行,比如源檔案test.cpp,最終要產生可執行檔test。
我們在shell中輸入如下命令
$ g++ -c test.cpp 產生test.o
$ g++ -o test test.o 產生可執行檔test
如果此時項目有兩個源檔案test.cpp test2.cpp 那編譯就需要多加一行。會使整個過程變複雜的情況有:
(1) 源檔案很多
(2) 需要很多庫檔案
(3) g++命令需要很多參數
(4) 檔案之間的依賴關係變的複雜
(5) 需要修改某一個源檔案時
當這些情況發生時,我們再從命令列去敲就會很不方便,makefile就可以根據規則和依賴關係來簡化我們的工作。在檔案修改時,也只需要敲入make或gmake命令即可。總體上makefile的結構是:
比如產生test.o的那一步可以寫成
同時,makefile還可以定義偽目標,比如clean就是最常用的目標,它用來清理產生的臨時檔案以及可執行檔。下面是一個簡單完整的makefile檔案
執行 $ make時就可以產生test檔案
執行 $ make clean 就可以清除目標檔案和可執行檔
當然makefile還提供很多便利的文法,比如一些宏替換和函數。
對於一個工程來說,該makefile很簡潔,因為它所有的產生目標檔案的命令只需要一行,一個SRCFILE可能相當於很多源檔案。CFLAGS替換了一些編譯選項,用了wildcard函數和萬用字元*後,$(wildcard *.cpp) 就等同於目前的目錄下所有源檔案。同樣,patsubst就是按格式替換,在第7行中是將SRCFILE宏中所有.cpp替換為.o,這正是我們需要的所有目標檔案。12、13行會將目錄下的cpp檔案挨個編譯。當我們需要修改時,只需要修改很少的一部分,比如要給編譯選項加入Werror,只需要修改第三行即可。當然一個好的makefile檔案還需要恰當的注釋,makefile採用’#’符號進行行注釋。
三、g++
g++是GNU C++編譯器。它在執行編譯工作需要4步:
- 預先處理,產生.i的檔案[前置處理器cpp]
- 將預先處理後的檔案轉換成組合語言,組建檔案.s[編譯器egcs]
- 由彙編變為目標代碼(機器代碼)產生.o的檔案[彙編器as]
- 串連目標代碼,產生可執行程式[連結器ld]
在我們的平時使用過程中,前三步都是一步完成。比如我們執行
$ g++ -c test.cpp
就可以直接產生了test.o。g++在使用過程中經常需要根據自己的需求加入一些選項,常用的選項和解釋如下:
-g/-ggdb -g是在編譯的時候產生調試資訊,只有加入此選項產生可執行檔才能用gdb調試,-ggdb選項是讓編譯過程儘可能多的產生調試資訊。
-O0/-O1/-O2/-O3 這是編譯器的4個最佳化層級,從左往右最佳化層級變高。
-Wall 是warn all的縮寫,它會顯示所有g++可以告訴我們的警告資訊。
-Werror 將所有的警告變成錯誤,即該條命令執行失敗,和-Wall結合使用。
-Wunused-parameter 當函數定義的形參在函數中沒有用到時,就會產生警告資訊。
-Wformat 該選項用於檢查在調用printf和scanf等函數時,格式串是否正確。若不正確則產生警告資訊。該選項已經包含在-Wall中了。 例printf("abc\n", i);
-Wconversion 一些類型轉化讓一個值發生變化時就產生警告資訊,比如浮點數和定點數之間的轉化,還有負整數到非負類型的隱式轉化(如unsigned int x = -1)。在實際中,感覺加不加該選項都會有同樣的警告。
-Wdeprecated 使用已淘汰或者棄用的檔案產生警告,如在c++程式中採用標頭檔iostream.h
-finline-functions 該選項將所有將所有簡單函數的代碼直接嵌入到調用者的代碼中,如果對一個給定函數的所有調用都是採用內嵌程式碼的方式,並且該函式宣告為靜態函數,那麼該函數將不會輸出為彙編代碼。該選項只有在-O3最佳化選項下生效。
四、gdb
一般來說,GDB主要幫忙你完成下面四個方面的功能:
- 啟動你的程式,可以按照你的自訂的要求隨心所欲的運行程式。
- 可讓被調試的程式在你所指定的調置的斷點處停住。(斷點可以是條件運算式)
- 當程式被停住時,可以檢查此時你的程式中所發生的事。
- 動態改變你程式的執行環境。
常用的一些功能及相應命令如下:
啟動gdb: $ gdb $file test 或者直接 $ gdb test
退出gdb: $ quit即可
運行程式: $ run
查看資訊: $ info
查看斷點資訊: $ info break
查看觀察點資訊: $ info watchpoint
查看當前來源程式: $ info source
查看堆棧資訊: $ info stack
查看當前參數: $ info args
列出一段來源程式: $ list
列出某個函數: $ list
以當前源檔案的某行為中間顯示一段來源程式: $ list LINENUM
接著前一次繼續顯示: $ list
顯示前一次之前的來源程式: $ list -
顯示另一個檔案的一段程式: $ list FILENAME 或$ list FILENAME:LINENUM
斷點操作: $ break
在函數入口設定斷點: $ break
在當前源檔案的某一行設定斷點: $ break LINENUM
在另一個源檔案的某一行設定斷點: $ break FILENAME:LINENUM
啟用斷點: $ enable 2
禁止斷點: $ disable 2
清除斷點: $ delete 2
觀察點操作
$ watch EXPR
我們知道斷點是當程式執行到某一程式碼時中斷,而觀察點是當程式訪問某個儲存單元時中斷,如果我們不知道某個儲存單元是在哪裡被改動的,這時候觀察點尤其有用。
$ rwatch EXPR:
使用EXPR作為運算式設定一個斷點,當EXPR被程式讀取時,程式被gdb暫停;
$ awatch EXPR:
使用EXPR作為運算式設定一個觀察點,當EXPR被讀出然後被寫入時,gdb會暫停程式;這個命令常
進入下一條語句 next step可以進入函數
退出函數, finish
線程操作
A、thread THREAD_NO: 該命令用於線上程之間進行切換,把線程號為THREAD_NO(gdb設定的線程號)的線程設定為當前線程;
B、info threads: 查詢當前進程所擁有的所有線程的狀態摘要資訊;gdb按照順序顯示:
a、線程號: gdb為被調試進程中的線程設定的順序號;
b、目標系統的線程標識;
3、此線程的當前棧資訊;
一些前面帶'*'號的線程,表示該線程是當前線程;
C、thread apply [THREAD_NO] [ALL] ARGS: 該命令用於向線程提供命令;
查看變數, print 檢查記憶體值的指令是x,x是examine的意思。用法如下:
x /NFU ADDR
其中N代表重複數,F代表輸出格式,U代表每個資料單位的大小。U可以取如下值:
b :位元組(byte)
h :雙位元組數值
w :四位元組數值
g :八位元組數值
因此,上面的指令可以這樣解釋:從ADDR地址開始,以F格式顯示N個U數值。例如:
x/4ub 0x4000
會以無符號十進位整數格式(u)顯示四個位元組(b),0x4000,0x4001,0x4002,0x4003。預設情況下,輸出格式依賴於它的資料類型。但你可以改變輸出格式。當你使用print命令時,可以用一個參數/F來選擇輸出的列印格式。F可以是以下的一些值:
'x' 16進位整數格式
'd' 有符號十進位整數格式
'u' 無符號十進位整數格式
'f' 浮點數格式
再比如char input[5];
(gdb) x/7b input
x命令列印指定儲存單元的內容。7b是列印格式,b表示每個位元組一組,7表示列印7組,從input數組的第一個位元組開始連續列印7個位元組。
切換棧
bt 查看堆棧資訊
up n 向上n層
down n 向下n層
frame 會列印出這些資訊:棧的層編號,當前的函數名,函數參數值,函數所在檔案及行號,函數執行到的語句。
用gdb調試core檔案
core dump又叫核心轉儲, 當程式運行過程中發生異常, 程式異常退出時, 由作業系統把程式當前的記憶體狀況儲存在一個core檔案中, 叫core dump。 (linux中如果記憶體越界會收到SIGSEGV訊號,然後就會core dump)
在程式啟動並執行過程中,有的時候我們會遇到Segment fault(段錯誤)這樣的錯誤。這種看起來比較困難,因為沒有任何的棧、trace資訊輸出。該種類型的錯誤往往與指標操作相關。往往可以通過這樣的方式進行定位。
造成segment fault,產生core dump的可能原因:
1、記憶體訪問越界
a) 由於使用錯誤的下標,導致數組訪問越界
b) 搜尋字串時,依靠字串結束符來判斷字串是否結束,但是字串沒有正常的使用結束符
c) 使用strcpy, strcat, sprintf, strcmp, strcasecmp等字串操作函數,將目標字串讀/寫爆。應該使用strncpy, strlcpy, strncat, strlcat, snprintf, strncmp, strncasecmp等函數防止讀寫越界。
2、 多線程程式使用了線程不安全的函數。
3、 多線程讀寫的資料未加鎖保護。
對於會被多個線程同時訪問的全域資料,應該注意加鎖保護,否則很容易造成core dump
4、 非法指標
a) 使用null 指標
b) 隨意使用指標轉換。一個指向一段記憶體的指標,除非確定這段記憶體原先就分配為某種結構或類型,或者這種結構或類型的數組,否則不要將它轉換為這種結構或類型的指標,而應該將這段記憶體拷貝到一個這種結構或類型中,再訪問這個結構或類型。這是因為如果這段記憶體的開始地址不是按照這種結構或類型對齊的,那麼訪問它 時就很容易因為bus error而core dump.
5、 堆疊溢位
不要使用大的局部變數(因為局部變數都分配在棧上),這樣容易造成堆疊溢位,破壞系統的棧和堆結構,導致出現莫名其妙的錯誤。
配置作業系統使其產生core檔案
首先通過ulimit命令查看一下系統是否配置支援了dump core的功能。通過ulimit -c或ulimit -a,可以查看core file大小的配置情況,如果為0,則表示系統關閉了dump core。可以通過ulimit -c unlimited來開啟。若發生了段錯誤,但沒有core dump,是由於系統禁止core檔案的產生。
解決方案:
$ulimit -c unlimited (只對當前shell進程有效)
或在~/.bashrc 的最後加入: ulimit -c unlimited (一勞永逸)
# ulimit -c
0
$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
file size (blocks, -f) unlimited
用gdb查看core檔案
發生core dump之後, 用gdb進行查看core檔案的內容, 以定位檔案中引發core dump的行.
$ gdb [exec file] [core file]
如: $ gdb ./test test.core
然後使用 bt查看呼叫堆疊。
五、twiki
這部分感覺在wiki嘗試幾次就可以熟悉了。