天天看點

GNU C 與 ANSI C 的一些差别

Linux 上可用的 C 編譯器是 GNU C 編譯器,它建立在自由軟體基金會的程式設計許可證的基礎上,是以可以自由釋出。 GNU C 對标準 C 進行一系列擴充,以增強标準 C 的功能。

1. 零長度和變量長度數組  一般不這樣用

GNU C 允許使用零長度數組,在定義變長對象的頭結構時,這個特性非常有用。例如:

  1. struct var_data {
  2. int len;
  3. char data[0];
  4. };

char data[0] 僅 僅 意 味 着 程 序 中 通 過 var_data 結 構 體 實 例 的 data[index] 成 員 可 以 通路 len 之 後 的 第 index 個 地 址, 它 并 沒 有 為 data[] 數 組 分 配 内 存, 因 此 sizeof(struct var_data)=sizeof(int)。

假設 struct var_data 的資料域就儲存在 struct var_data 緊接着的記憶體區域中,則通過如下代碼可以周遊這些資料:

  1. struct var_data s;
  2. ...
  3. for (i = 0; i < s.len; i++)
  4. printf("%02x", s.data[i]);

GNU C 中也可以使用 1 個變量定義數組,例如如下代碼中定義的“double x[n]”:

int main (int argc, char *argv[])

{

int i, n = argc;

double x[n];

for (i = 0; i < n; i++)

x[i] = i;

return 0;

}

2. case 範圍

GNU C 支援 case x…y 這樣的文法,區間 [x,y] 中的數都會滿足這個 case 的條件,請看

下面的代碼:

switch (ch) {
case '0'... '9': c -= '0';
break;
case 'a'... 'f': c -= 'a' - 10;
break;
case 'A'... 'F': c -= 'A' - 10;
break;
}      

代碼中的 case '0'... '9' 等價于标準 C 中的:

case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':      

3. 語句表達式

GNU C 把包含在括号中的複合語句看成是一個表達式,稱為語句表達式,它可以出現在任何允許表達式的地方。我們可以在語句表達式中使用原本隻能在複合語句中使用的循環、局部變量等,例如:

#define min_t(type,x,y) \
({type _ _x =(x);type _ _y = (y); _ _x<_ _y? _ _x: _ _y; })
int ia, ib, mini;
float fa, fb, minf;
mini = min_t(int, ia, ib);
minf = min_t(float, fa, fb);      

因為重新定義了 _ _xx 和 _ _y 這兩個局部變量,是以用上述方式定義的宏将不會有副作用。在标準 C 中,對應的如下宏則會産生副作用:

#define min(x,y) ((x) < (y) ? (x) : (y))      

代碼 min(++ia,++ib) 會展開為 ((++ia) < (++ib) ? (++ia): (++ib)),傳入宏的“參數”增加兩次。

4. typeof 關鍵字

typeof(x) 語句可以獲得 x 的類型,是以,可以借助 typeof 重新定義 min 這個宏:

#def ine min(x,y) ({ \
const typeof(x) _x = (x); \
const typeof(y) _y = (y); \
(void) (&_x == &_y); \
_x < _y ? _x : _y; })      

我們不需要像 min_t(type,x,y) 那個宏那樣把 type 傳入,因為通過 typeof(x)、 typeof(y) 可以獲得 type。代碼行 (void) (&_x == &_y) 的作用是檢查 _x 和 _y 的類型是否一緻。

5. 可變參數宏

标準 C 就支援可變參數函數,意味着函數的參數是不固定的,例如 printf() 函數的原型為:

int printf( const char *format [, argument]... );

而在 GNU C 中,宏也可以接受可變數目的參數,例如:

#def ine pr_debug(fmt,arg...) \

printk(fmt,##arg)

這裡 arg 表示其餘的參數,可以有零個或多個參數,這些參數以及參數之間的逗号構成arg 的值,在宏擴充時替換 arg,如下列代碼:

pr_debug("%s:%d",f ilename,line)

會被擴充為:

printk("%s:%d", f ilename, line)

使用“##”是為了處理 arg 不代表任何參數的情況,這時候,前面的逗号就變得多餘了。

使用“##”之後, GNU C 預處理器會丢棄前面的逗号,這樣,下列代碼:

pr_debug("success!\n")

會被正确地擴充為:

printk("success!\n")

而不是:

printk("success!\n",)

這正是我們希望看到的。

6. 标号元素

标準 C 要求數組或結構體的初始化值必須以固定的順序出現,在 GNU C 中,通過指定索引或結構體成員名,允許初始化值以任意順序出現。

指定數組索引的方法是在初始化值前添加“[INDEX] =”,當然也可以用“[FIRST ...LAST] =”的形式指定一個範圍。例如,下面的代碼定義了一個數組,并把其中的所有元素指派為 0:

unsigned char data[MAX] = { [0 ... MAX-1] = 0 };

下面的代碼借助結構體成員名初始化結構體:

struct f ile_operations ext2_f ile_operations = {
llseek: generic_f ile_llseek,
read: generic_f ile_read,
write: generic_f ile_write,
ioctl: ext2_ioctl,
mmap: generic_f ile_mmap,
open: generic_f ile_open,
release: ext2_release_f ile,
fsync: ext2_sync_f ile,
};      

但是, Linux 2.6 推薦類似的代碼應該盡量采用标準 C 的方式:

struct f ile_operations ext2_f ile_operations = {
.llseek = generic_f ile_llseek,
.read = generic_f ile_read,
.write = generic_f ile_write,
.aio_read = generic_f ile_aio_read,
.aio_write = generic_f ile_aio_write,
.ioct = ext2_ioctl,
.mmap = generic_f ile_mmap,
.open = generic_f ile_open,
.release = ext2_release_f ile,
.fsync = ext2_sync_f ile,
.readv = generic_f ile_readv,
.writev = generic_f ile_writev,
.sendf ile = generic_f ile_sendf ile,
};      

7. 目前函數名

GNU C 預定義了兩個辨別符儲存目前函數的名字, _ _FUNCTION_ _ 儲存函數在源碼中的名字, _ _PRETTY_FUNCTION_ _ 儲存帶語言特色的名字。在 C 函數中,這兩個名字是相同的。

void example()

{

printf("This is function:%s", _ _FUNCTION_ _);

}

代碼中的 _ _FUNCTION_ _ 意味着字元串“example”。 C99 已經支援 _ _func_ _ 宏,是以建議在 Linux 程式設計中不再

使用 _ _FUNCTION_ _,而轉而使用 _ _func_ _:

void example(void)

{

printf("This is function:%s", _ _func_ _);

}

8. 特殊屬性聲明

GNU C 允許聲明函數、變量和類型的特殊屬性,以便手動優化代碼和定制代碼檢查的方法。要指定一個聲明的屬性,隻需要在聲明後添加 _ _attribute_ _ (( ATTRIBUTE ))。其中 ATTRIBUTE 為屬性說明,如果存在多個屬性,則以逗号分隔。 GNU C 支援 noreturn、

format、 section、 aligned、 packed 等十多個屬性。

noreturn 屬性作用于函數,表示該函數從不傳回。這會讓編譯器優化代碼,并消除不必

要的警告資訊。例如:

# def ine ATTRIB_NORET _ _attribute_ _((noreturn)) ....
asmlinkage NORET_TYPE void do_exit(long error_code) ATTRIB_NORET;      

format 屬性也用于函數,表示該函數使用 printf、 scanf 或 strftime 風格的參數,指定

format 屬性可以讓編譯器根據格式串檢查參數類型。例如:

asmlinkage int printk(const char * fmt, ...) _ _attribute_ _ ((format (printf, 1, 2)));

上述代碼中的第 1 個參數是格式串,從第 2 個參數開始都會根據 printf() 函數的格式串規則檢查參數。

unused 屬性作用于函數和變量,表示該函數或變量可能不會用到,這個屬性可以避免編譯器産生警告資訊。

aligned 屬性用于變量、結構體或聯合體,指定變量、結構體或聯合體的對齊方式,以位元組為機關,例如:

struct example_struct {
    char a;
    int b;
    long c;
} _ _attribute_ _((aligned(4)));      

表示該結構類型的變量以 4 位元組對齊。

packed 屬性作用于變量和類型,用于變量或結構體成員時表示使用最小可能的對齊,用于枚舉、結構體或聯合體類型時表示該類型使用最小的記憶體。例如:

struct example_struct {
    char a;
    int b;
    long c;
}_ _attribute_ _((packed));      

編譯器對結構體成員及變量對齊的目的是為了更快地通路結構體成員及變量占據的記憶體。例如,對于一個 32 位的整型變量,若以 4 位元組方式存放(即低兩位位址為 00),則 CPU 在一個總線周期内就可以讀取 32 位; 否則, CPU 需要兩個總線周期才能讀取 32 位。

9. 内建函數

GNU C 提供了大量内建函數,其中大部分是标準 C 庫函數的 GNU C 編譯器内建版本,例如 memcpy() 等,它們與對應的标準 C 庫函數功能相同。

不屬于庫函數的其他内建函數的命名通常以 _ _builtin 開始,如下所示。

● 内建函數 _ _builtin_return_address (LEVEL) 傳回目前函數或其調用者的傳回位址,參數 LEVEL 指定調用棧的級數,如 0 表示目前函數的傳回位址, 1 表示目前函數的調用者的傳回位址。

● 内建函數 _ _builtin_constant_p(EXP) 用于判斷一個值是否為編譯時常數,如果參數EXP 的值是常數,函數傳回 1,否則傳回 0。

例如,下面的代碼可檢測第 1 個參數是否為編譯時常數以确定采用參數版本還是非參數

版本:

#def ine test_bit(nr,addr) \

(_ _builtin_constant_p(nr) ? \

constant_test_bit((nr),(addr)) : \

variable_test_bit((nr),(addr)))

● 内建函數 _ _builtin_expect(EXP, C) 用于為編譯器提供分支預測資訊,其傳回值是整數表達式 EXP 的值, C 的值必須是編譯時常數。

Linux 内 核 編 程 時 常 用 的 likely() 和 unlikely() 底 層 調 用 的 likely_notrace()、 unlikely_notrace() 就是基于 _ _builtin_expect(EXP, C) 實作的。

#def ine likely_notrace(x) __builtin_expect(!!(x), 1)

#def ine unlikely_notrace(x) __builtin_expect(!!(x), 0)

若代碼中出現分支,則即可能中斷流水線,我們可以通過 likely() 和 unlikely() 暗示分支容易成立還是不容易成立,例如:

if (likely(!IN_DEV_ROUTE_LOCALNET(in_dev)))

if (ipv4_is_loopback(saddr))

goto e_inval;

在使用 gcc 編譯 C 程式的時候,如果使用“-ansi –pedantic”編譯選項,則會告訴編譯器不使用 GNU 擴充文法。例如對于如下 C 程式 test.c:

struct var_data {

int len;

char data[0];

};

struct var_data a;

直接編譯可以通過:

gcc -c test.c

如果使用“-ansi –pedantic”編譯選項,編譯會報警:

gcc -ansi -pedantic -c test.c

test.c:3: warning: ISO C forbids zero-size array 'data'

繼續閱讀