首頁 > 軟體

Linux入門學習教學:GNU C及將Vim打造成C/C++的半自動化IDE

2020-06-16 18:06:30

C語言在Linux系統中的重要性自然是無與倫比、不可替代,所以我寫Linux江湖系列不可能不提C語言。C語言是我的啟蒙語言,感謝C語言帶領我進入了程式世界。雖然現在不靠它吃飯,但是仍免不了經常和它打交道,特別是在Linux系統下。

  Linux系統中普遍使用的是GNU-C,這裡有一份Gnu-C語言手冊.pdf,下載地址:。

------------------------------------------分割線------------------------------------------

免費下載地址在 http://linux.linuxidc.com/

使用者名稱與密碼都是www.linuxidc.com

具體下載目錄在 /2015年資料/3月/2日/Linux入門學習教學:GNU C及將Vim打造成C&C++的半自動化IDE/

下載方法見 http://www.linuxidc.com/Linux/2013-07/87684.htm

------------------------------------------分割線------------------------------------------

The GNU C Reference Manual的主頁在這裡:http://www.gnu.org/software/gnu-c-manual/。C語言的核心極其緊湊,該手冊總共只有91頁,去掉目錄、附錄和索引後就只有70頁。我一般一個多小時就可以將其從頭至尾復習一遍。我曾有過將其翻譯成中文的想法,後來還是放棄了。翻譯這種字斟句酌的事情還是讓別人來幹吧。我只寫寫我自己的感悟。

感悟一:C語言標準乾不過GNU擴充套件

   最近為了研究X Window的底層協定,開始嘗試使用XCB程式設計。當我開啟XCB的標頭檔案的時候,我被大量的__restrict__關鍵字驚呆了,好在有GNU C語言手冊為我答疑解惑。__restrict__又是一個GNU擴充套件的關鍵字,後面我會詳細講解該關鍵字的用途。其實C語言的C99標準中已經引入了restrict關鍵字,沒有前後的下劃線,但是在大量的開原始碼中,使用最普遍的還是GNU的擴充套件,而不是C語言標準。

  和restrict關鍵字有相同命運的還有inline、_Complex等,它們都是在C99標準中引入的關鍵字,但是其實在C99標準出來之前,GNU C中早就有了__inline__、__complex__等擴充套件關鍵字。還記得多年前我學習Linux 0.11版的原始碼時,看到大量的__inline__曾經疑惑不已,不知道為什麼Linus在91年就能用上了如此先進的語言功能,後來才知道,這是GNU的擴充套件關鍵字。

  C語言的標準有C89和C99,使用GCC的時候甚至要顯示指定-std=c99才能全面支援C99標準,所以在開源界,大家還是喜歡首選GNU的擴充套件關鍵字。比如__inline__、__complex__和__restrict__。總而言之,C語言標準乾不過GNU擴充套件。

  下面來看看__restrict__的真正含義。載過一篇文章《為什麼有些語言會比別的快》,其中提到“很長一段時間,相同的兩個程式在Fortran和C(或者C++)中執行,Fortran會快一些,因為Fortran的優化做的更好。這是真的,就算C語言和Fortran的編譯器用了相同的程式碼生成器也是一樣。這個不同不是因為Fortran的某種特性,事實上恰恰相反,是因為Fortran不具備的特性。”這是因為C語言中的指標給編譯器的優化帶來了困難,文章中繼續說道:“問題就來了。這些指標可以代替任何記憶體地址。更重要的是,他們可以重疊。輸出陣列的記憶體地址也可以同時是輸入陣列的。甚至可以部分重疊,輸出陣列可以覆蓋一個輸入陣列的一半。這對編譯器優化來說是個大問題,因為之前基於陣列的優化不再適用。特別的,新增元素的順序也成問題,如果輸出重疊的陣列,計算的結果會變得不確定,取決於輸出元素的動作是發生在元素被覆蓋之前還是之後。”

  有了__restrict__,C語言的該問題將不復存在。用__restrict__修飾一個指標後,①該指標只能在定義的時候被初始化;②不會再有別的指標指向該指標指向的記憶體,因此編譯器可以對演算法進行優化。如下程式碼:

int * __restrict__ p = (int*)malloc(100*sizeof(int));

指標p有__restrict__關鍵字修飾,所以它只能在定義的時候被初始化,以後不能賦值,而沒有__restrict__修飾的指標,可以隨時賦值,如下:

int arr[100];
int* pArr;
pArr = arr;

指標pArr沒有被__restrict__關鍵字修飾,所以可以將陣列的首地址賦值給它。

  比如我們定義一個函數對兩塊資料進行操作,結果放入第3塊記憶體,如下:

void func1(void* p1, void* p2, void* p3, int size){
    for(int i=0; i<size; i++){
        p3[i] = p1[i] + p2[i];
    }
}

很顯然,由於編譯器沒辦法判斷指標p1、p2、p3指向的記憶體是否重疊,所以無法進行優化,加上__restrict__關鍵字後,如下:

void func1(void* __restrict__ p1, void* __restrict__ p2, void* __restrict__ p3, int size){
    for(int i=0; i<size; i++){
        p3[i] = p1[i] + p2[i];
    }
}

相當於明確告訴編譯器這幾塊記憶體不會重疊,所以編譯器就可以放心大膽對程式進行優化。

   另一個關鍵字是_Complex,C99才引入,而且需要包含<complex.h>標頭檔案。其實在GNU C中,早就有__complex__、__real__、__imag__等擴充套件關鍵字。如下程式碼:

 1 #include <stdlib.h>
 2 #include <stdio.h>
 3 
 4 int main(){
 5     __complex__ a = 3 + 4i;
 6     __complex__ b = 5 + 6i;
 7     __complex__ c = a + b;
 8     __complex__ d = a * b;
 9     __complex__ e = a / b;
10     printf("a + b = %f + %fin", __real__ c, __imag__ c);
11     printf("a * b = %f + %fin", __real__ d, __imag__ d);
12     printf("a / b = %f + %fin", __real__ e, __imag__ e);
13     return 0;
14 }

  可以看到,在C語言中也可以直接對複數進行計算。數值計算再也不是Fortran的專利。

感悟二:指標和陣列還真是不一樣

  從學C語言開始,老師就教導我們說指標和陣列是一樣的,它們可以用同樣的方式進行操作。而事實上,指標和陣列還是有差別的。直到多年後讀《C專家程式設計》,才直到所謂指標和陣列一樣是一個美麗的錯誤,只是因為在《The C Programming Language》這本書裡,把“作為函數引數時,指標和陣列一樣”這樣一句話前後分開分別印到了兩頁而已。

  比如,指標不儲存資料的長度資訊,而陣列有,如下程式碼:

 1 #include <stdlib.h>
 2 #include <stdio.h>
 3 
 4 int main(){
 5     int* p = (int*)malloc(100*sizeof(int));
 6     int arr[100] = {0};
 7     printf("The size of p: %dn", sizeof(p));
 8     printf("The size of arr: %dn", sizeof(arr));
 9     return 0;
10 }

這段程式碼的執行結果為:

The size of p: 8
The size of arr: 400

  我們經常可以使用如下的程式碼片段來獲得一個陣列中有多少個元素,如下:

int arr[100];
size_t length = sizeof(arr)/sizeof(int);

  但是,當使用陣列作為函數的引數的時候,陣列會退化成指標。如下程式碼:

 1 #include <stdlib.h>
 2 #include <stdio.h>
 3 
 4 void test_array(int arr[]){
 5     printf("The size of arr in function: %dn", sizeof(arr));
 6     return;
 7 }
 8 
 9 int main(){
10     int arr[100] = {0};
11     printf("The size of arr in main: %dn", sizeof(arr));
12     test_array(arr);
13     return 0;
14 }

這段程式碼的執行結果為:

The size of arr in main: 400
The size of arr in function: 8

感悟三:C語言中的不完全型別(Incomplete Types)

   在GNU C中可以定義不完全型別,不完全型別主要有兩種,一種是空的結構,一種是空的陣列,比如:

struct point;
char name[0];

空的結構不能定義變數,只能使用空結構的指標。空結構可以在後面再將它補充完整,如下:

struct point{
    int x,y;
};

空結構在定義連結串列的時候經常用到,如下:

struct linked_list{
    struct linked_list* next;
    int x;
    /*other elements here perhaps */
}
struct linked_list* head;


  還有一種不完全型別就是將一個結構的最後一項定義為一個空的陣列,這樣可以用來表示一個可變長度的結構或陣列,演示該技術的程式碼如下:

 1 #include <stdlib.h>
 2 #include <stdio.h>
 3 
 4 typedef struct {
 5     int length;
 6     int arr[0];
 7 } incomplete_type;
 8 
 9 int main(){
10     char hello[] = "Hello, world!";
11     int length = sizeof(hello) / sizeof(char);
12     incomplete_type* p = (incomplete_type*)malloc(sizeof(int) + length*sizeof(char));
13     p->length = length;
14     for(int i=0; i<p->length; i++){
15         p->arr[i] = hello[i];
16     }
17     printf("p->length=%dn", p->length);
18     printf("p->arr=%sn", p->arr);
19 }

打造C/C++的IDE

  後面的內容展示如何將Vim打造成一個半自動的C/C++ IDE。讀過我的Java部落格的朋友應該知道,其實我更喜歡用Eclipse。只有在需要寫非常簡單的程式(比如做習題)的情況下,我才會用Vim。這在我的《打造屬於自己的Vim》中有論述。在這篇文章中我展示了怎麼使用Vundle管理外掛以及怎麼怎麼閱讀幫助文件,同時展示了taglist.vim的簡單用法。如果要用Vim來寫C/C++程式,還需要做少許擴充套件。

  第一,安裝以下幾個外掛,由於使用Vundle管理外掛,所以只需要把外掛名寫入.vimrc組態檔,然後執行:BundleInstall即可,如下圖:

  分別介紹一下這幾個外掛。The-NERD-tree是一個瀏覽目錄和檔案的外掛,可以使用:help NERD_tree.txt檢視它的幫助文件。taglist.vim是瀏覽符號以及在符號之間跳轉的外掛,使用:help taglist.txt檢視它的幫助文件。a.vim是在原始碼檔案和標頭檔案之間跳轉的外掛,不需要幫助文件,它的命令就是:A。c.vim是提供IDE功能的主要外掛,它提供的功能有自動註釋、反註釋、自動插入程式碼塊及自動執行,如果安裝了splint,還可以對程式碼進行靜態檢查,使用:help csupport.txt檢視它的文件。OmniCppComplete是一個提供自動補全功能的外掛,使用:help omnicppcomplete.txt檢視它的文件。

  這些外掛中,taglist.vim和OmniCppComplete需要ctags軟體的支援,所以需要安裝exuberant-ctags軟體包,在Fedora 20中,只需要使用yum install ctags即可自動安裝。

  第二,生成tags資料庫,並將其加入到Vim中。

  我們寫C程式的時候,使用到的檔案主要存在於兩個地方,一個是我們工作的當前目錄,另外一個是/usr/include。所以要到/usr/include目錄下使用ctags命令生成tags資料庫檔案。為了使tags資料庫中包含儘可能多的資訊(結構、列舉、類、函數、宏定義等等),需要指定ctags的引數,如下:

  然後將該tags檔案的路徑加入到.vimrc組態檔中,同時設定一個鍵盤對映,使得按Ctrl+F12時,在工作目錄中呼叫ctags命令。如下組態檔的最後兩行:

  然後,在使用Vim寫C程式的時候,如果輸入了.、->這樣的元素,則其成員會自動補全。如果輸入的是一個字串(比如函數名),可以按Ctrl-X Ctrl-O呼叫自動補全,如下圖:

  不僅會彈出候選視窗,而且在最上面的視窗中會顯示函數的完整的簽名,及其所在的檔案。這對於我們經常記不全函數名、記不清函數簽名的人來說,已經是莫大的福音了。

  taglist.vim和OmniCppComplete外掛提供的功能用起來都只需要一個命令,而c.vim提供的命令就比較多了。而且在c.vim的幫助文件中並沒有列出所有功能的命令,有一個辦法可以學習這些命令,那就是開啟GVim,通過GVim選單中的C/C++選單來學習c.vim提供的功能和命令。

  相比網上其它的將Vim打造成IDE的文章,我的設定比較簡單,基本上只安裝了幾個外掛,而沒有做過多的設定。當我需要某個功能的時候,我會使用命令顯式地呼叫它,所以,稱它為半自動化IDE吧。

--------------------------------------分割線 --------------------------------------

把VIM打造成一個簡單實用的IDE http://www.linuxidc.com/Linux/2011-06/37032.htm

Vim學習指南 http://www.linuxidc.com/Linux/2013-08/89096.htm

快速學會 Vi編輯器 http://www.linuxidc.com/Linux/2013-08/88586.htm

強大的Vim 編輯器 http://www.linuxidc.com/Linux/2013-07/87544.htm

CentOS 6.2上搭建Vim開發環境 http://www.linuxidc.com/Linux/2013-07/87363.htm

CentOS 5.4 安裝高亮Vim編輯工具 http://www.linuxidc.com/Linux/2013-06/86508.htm

Vim技巧分享:C語言設定 http://www.linuxidc.com/Linux/2012-12/77124.htm

Ubuntu中設定Vim的行號 http://www.linuxidc.com/Linux/2012-12/75485.htm

Vim編輯器使用基礎教學 http://www.linuxidc.com/Linux/2013-05/84031.htm

--------------------------------------分割線 --------------------------------------


IT145.com E-mail:sddin#qq.com