Linux 上可用的 C 編譯器是 GNU C 編譯器,這個編譯器是建立在自由軟體基金會的程式設計許可證之上的,因此可以自由地發布和使用。 GNU C 對標準 C 進行了一系列的擴展,以增強標準 C 的功能。
GNU C 允許使用零長度數組,在定義變長物件的頭結構時,這個特性非常有用。例如:
struct var_data { int len; char data[0]; };
char data[0]只是意味著程式中透過var_data結構體實例的data[index]成員可以存取len之後的第index個位址,它並沒有為data[]陣列分配內存,因此sizeof(struct var_data )=sizeof(int)。
假設struct var_data的資料域就保存在struct var_data緊接著的記憶體區域中,則透過以下程式碼可以遍歷這些資料:
struct var_data s; ... for (i = 0; i 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 return 0; }
#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':
GNU C把包含在括號中的複合語句看成是一個表達式,稱為語句表達式,它可以出現在任何允許表達式的地 方。我們可以在語句表達式中使用原本只能在複合語句中使用的迴圈、局部變數等,例如:
#define min_t(type,x,y) \ ( { type _ _x =(x);type _ _y = (y); _ _xfloat fa, fb, minf; mini = min_t(int, ia, ib); minf = min_t(float, fa, fb);
因為重新定義了__xx和__y這兩個局部變量,所以用上述方式定義的巨集將不會有副作用。在標準C中,對應的如 下巨集則會產生副作用:
#define min(x,y) ((x)
程式碼min( ia, ib)會展開為(( ia)
typeof(x)語句可以獲得x的類型,因此,可以藉助typeof重新定義min這個巨集:
#define min(x,y) ({ \ const typeof(x) _x = (x); \ const typeof(y) _y = (y); \ (void) (&_x == &_y); \ _x
我們不需要像min_t(type,x,y)那個巨集那樣把type傳入,因為透過typeof(x)、typeof(y)可以得到type。代 碼行(void)(&_x==&_y)的作用是檢查_x和_y的型別是否一致。
#標準C就支援可變參數函數,表示函數的參數是不固定的,例如printf()函數的原型為:
int printf( const char *format [, argument]... );
而在GNU C中,巨集也可以接受可變數目的參數,例如:
#define pr_debug(fmt,arg...) \ printk(fmt,##arg)
這裡arg表示其餘的參數,可以有零個或多個參數,這些參數以及參數之間的逗號構成arg的值,在宏擴展時替換 arg,如下列代碼:
pr_debug("%s:%d",filename,line)
會被擴充為:
printk("%s:%d", filename, line)
使用「##」是為了處理arg不代表任何參數的情況,這時候,前面的逗號就變得多餘了。使用「##」之後,GNU C預處理器會丟棄前面的逗號,這樣,下列程式碼:
pr_debug("success!\n")
會被正確地擴展為:
printk("success!\n")
而不是:
printk("success!\n",)
#標準C要求陣列或結構體的初始化值必須以固定的順序出現,在GNU C中,透過指定索引或結構體成員名,允許 初始化值以任意順序出現。
指定陣列索引的方法是在初始化值之前加上“[INDEX]=”,當然也可以用“[FIRST…LAST]=”的形式指定一個範圍。例如,下面的程式碼定義了一個數組,並將其中的所有元素賦值為0:
unsigned char data[MAX] = { [0 ... MAX-1] = 0 };
下面的程式碼借助結構體成員名初始化結構體:
struct file_operations ext2_file_operations = { llseek: generic_file_llseek, read: generic_file_read, write: generic_file_write, ioctl: ext2_ioctl, mmap: generic_file_mmap, open: generic_file_open, release: ext2_release_file, fsync: ext2_sync_file, };
但是,Linux 2.6推薦類似的程式碼應該盡量採用標準C的方式:
struct file_operations ext2_file_operations = { .llseek = generic_file_llseek, .read = generic_file_read, .write = generic_file_write, .aio_read = generic_file_aio_read, .aio_write = generic_file_aio_write, .ioct = ext2_ioctl, .mmap = generic_file_mmap, .open = generic_file_open, .release = ext2_release_file, .fsync = ext2_sync_file, .readv = generic_file_readv, .writev = generic_file_writev, .sendfile = generic_file_sendfile, };
#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__); }
GNU C允许声明函数、变量和类型的特殊属性,以便手动优化代码和定制代码检查的方法。要指定一个声明的 属性,只需要在声明后添加__attribute__((ATTRIBUTE))。其中ATTRIBUTE为属性说明,如果存在多个属 性,则以逗号分隔。GNU C支持noreturn、format、section、aligned、packed等十多个属性。
noreturn属性作用于函数,表示该函数从不返回。这会让编译器优化代码,并消除不必要的警告信息。例如:
# define 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位。
GNU C提供了大量内建函数,其中大部分是标准C库函数的GNU C编译器内建版本,例如memcpy()等,它们与对应的标准C库函数功能相同。
不属于库函数的其他内建函数的命名通常以__builtin开始,如下所示。
内建函数__builtin_return_address(LEVEL)返回当前函数或其调用者的返回地址,参数LEVEL指定调用栈的级数,如0表示当前函数的返回地址,1表示当前函数的调用者的返回地址。
内建函数__builtin_constant_p(EXP)用于判断一个值是否为编译时常数,如果参数EXP的值是常数,函数返回1,否则返回0。例如,下面的代码可检测第1个参数是否为编译时常数以确定采用参数版本还是非参数版本:
#define 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)实现的。
#define likely_notrace(x) __builtin_expect(!!(x), 1) #define 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'
以上是Linux GNU C 與 ANSI C 有什麼不同?的詳細內容。更多資訊請關注PHP中文網其他相關文章!