閱讀273 返回首頁    go 阿裏雲 go 技術社區[雲棲]


[gcc核心擴展]關於gcc中的typeof以及其他東東

(使用以下擴展可能需要使用-gnu99)

 

GNC CC是一個功能非常強大的跨平台C編譯器,它對C 語言提供了很多擴展,這些擴展對優化、目標代碼布局、更安全的檢查等方麵提供了很強的支持。本文把支持GNU 擴展的C 語言稱為GNU C。

  Linux 內核代碼使用了大量的 GNU C 擴展,以至於能夠編譯 Linux 內核的唯一編譯器是 GNU CC,以前甚至出現過編譯 Linux 內核要使用特殊的 GNU CC 版本的情況。本文是對 Linux 內核使用的 GNU C 擴展的一個匯總,希望當你讀內核源碼遇到不理解的語法和語義時,能從本文找到一個初步的解答,更詳細的信息可以查看gcc.info。文中的例子取自 Linux 2.4.18。

  

  語句表達式

  


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

  ++++ include/linux/kernel.h

  159: #define min_t(type,x,y) /

  160: ({ type __x = (x); type __y = (y); __x < __y ? __x: __y; })

  ++++ net/ipv4/tcp_output.c

  654: int full_space = min_t(int, tp->window_clamp, tcp_full_space(sk));

  複合語句的最後一個語句應該是一個表達式,它的值將成為這個語句表達式的值。這裏定義了一個安全的求最小值的宏,在標準 C 中,通常定義為:

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

  這個定義計算 x 和 y 分別兩次,當參數有副作用時,將產生不正確的結果,使用語句表達式隻計算參數一次,避免了可能的錯誤。語句表達式通常用於宏定義。

  

  Typeof

  


  使用前一節定義的宏需要知道參數的類型,利用 typeof 可以定義更通用的宏,不必事先知道參數的類型,例如:

  ++++ include/linux/kernel.h

  141: #define min(x,y) ({ /

  142: const typeof(x) _x = (x); /

  143: const typeof(y) _y = (y); /

  144: (void) (&_x == &_y); /

  145: _x < _y ? _x : _y; })

  這裏 typeof(x) 表示 x 的值類型,第 142 行定義了一個與 x 類型相同的局部變量 _x 並初使化為 x,注意第 144 行的作用是檢查參數 x 和 y 的類型是否相同。typeof 可以用在任何類型可以使用的地方,通常用於宏定義。

  

  零長度數組

  


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

  ++++ include/linux/minix_fs.h

  85: struct minix_dir_entry {

  86: __u16 inode;

  87: char name[0];

  88: };

  結構的最後一個元素定義為零長度數組,它不占結構的空間。在標準 C 中則需要定義數組長度為 1,分配時計算對象大小比較複雜。

  

  可變參數宏

  


  在 GNU C 中,宏可以接受可變數目的參數,就象函數一樣,例如:

  ++++ include/linux/kernel.h

  110: #define pr_debug(fmt,arg...) /

  111: printk(KERN_DEBUG fmt,##arg)

  這裏 arg 表示其餘的參數,可以是零個或多個,這些參數以及參數之間的逗號構成 arg 的值,在宏擴展時替換 arg,例如:

  pr_debug("%s:%d",filename,line)

  擴展

  printk("<7>" "%s:%d", filename, line)

  使用 ## 的原因是處理 arg 不匹配任何參數的情況,這時 arg 的值為空,GNUC 預處理器在這種特殊情況下,丟棄 ## 之前的逗號,這樣

  pr_debug("success!/n")

  擴展

  printk("<7>" "success!/n")

  注意最後沒有逗號。

  

  標號元素


  標準 C 要求數組或結構變量的初使化值必須以固定的順序出現,在 GNU C 中,通過指定索引或結構域名,允許初始化值以任意順序出現。指定數組索引的方法是在初始化值前寫 '[INDEX] =',要指定一個範圍使用 '[FIRST ... LAST] =' 的形式,例如:

  +++++ arch/i386/kernel/irq.c

  1079: static unsigned long irq_affinity [NR_IRQS] = { [0 ... NR_IRQS-1] = ~0UL };

  將數組的所有元素初使化為 ~0UL,這可以看做是一種簡寫形式。要指定結構元素,在元素值前寫 'FIELDNAME:',例如:

  ++++ fs/ext2/file.c

  41: struct file_operations ext2_file_operations = {

  42: llseek: generic_file_llseek,

  43: read: generic_file_read,

  44: write: generic_file_write,

  45: ioctl: ext2_ioctl,

  46: mmap: generic_file_mmap,

  47: open: generic_file_open,

  48: release: ext2_release_file,

  49: fsync: ext2_sync_file,

  50 };

  將結構 ext2_file_operations 的元素 llseek 初始化為 generic_file_llseek,元素 read 初始化genenric_file_read,依次類推。我覺得這是 GNU C 擴展中最好的特性之一,當結構的定義變化以至元素的偏移改變時,這種初始化方法仍然保證已知元素的正確性。對於未出現在初始化中的元素,其初值為 0。

  

  Case 範圍

  


  GNU C 允許在一個 case 標號中指定一個連續範圍的值,例如:

  ++++ arch/i386/kernel/irq.c

  1062: case '0' ... '9': c -= '0'; break;

  1063: case 'a' ... 'f': c -= 'a'-10; break;

  1064: case 'A' ... 'F': c -= 'A'-10; break;

  case '0' ... '9':

  相當於

  case '0': case '1': case '2': case '3': case '4':

   case '5': case '6': case '7': case '8': case '9':

  

  聲明的特殊屬性

  


  GNU C 允許聲明函數、變量和類型的特殊屬性,以便手工的代碼優化和更仔細的代碼檢查。要指定一個聲明的屬性,在聲明後寫

   __attribute__ (( ATTRIBUTE ))

  其中 ATTRIBUTE 是屬性說明,多個屬性以逗號分隔。GNU C 支持十幾個屬性,這裏介紹最常用的:

  

  * noreturn

  


  屬性 noreturn 用於函數,表示該函數從不返回。這可以讓編譯器生成稍微優化的代碼,最重要的是可以消除不必要的警告信息比如未初使化的變量。例如:

  ++++ include/linux/kernel.h

  47: # define ATTRIB_NORET__attribute__((noreturn)) ....

  61: asmlinkage NORET_TYPE void do_exit(long error_code)

   ATTRIB_NORET;

  

  * format (ARCHETYPE, STRING-INDEX, FIRST-TO-CHECK)

  


  屬性 format 用於函數,表示該函數使用 printf, scanf 或 strftime 風格的參數,使用這類函數最容易犯的錯誤是格式串與參數不匹配,指定 format 屬性可以讓編譯器根據格式串檢查參數類型。例如:

  ++++ include/linux/kernel.h?

  89: asmlinkage int printk(const char * fmt, ...)

  90: __attribute__ ((format (printf, 1, 2)));

  表示第一個參數是格式串,從第二個參數起根據格式串檢查參數。

  

  * unused


  屬性 unused 用於函數和變量,表示該函數或變量可能不使用,這個屬性可以避免編譯器產生警告信息。

  

  * section ("section-name")

  


  屬性 section 用於函數和變量,通常編譯器將函數放在 .text 節,變量放在.data 或 .bss 節,使用 section 屬性,可以讓編譯器將函數或變量放在指定的節中。例如:

  ++++ include/linux/init.h

  78: #define __init __attribute__ ((__section__ (".text.init")))

  79: #define __exit __attribute__ ((unused, __section__(".text.exit")))

  80: #define __initdata __attribute__ ((__section__ (".data.init")))

  81: #define __exitdata __attribute__ ((unused, __section__ (".data.exit")))

  82: #define __initsetup __attribute__ ((unused,__section__ (".setup.init")))

  83: #define __init_call __attribute__ ((unused,__section__ (".initcall.init")))

  84: #define __exit_call __attribute__ ((unused,__section__ (".exitcall.exit")))

  連接器可以把相同節的代碼或數據安排在一起,Linux 內核很喜歡使用這種技術,例如係統的初始化代碼被安排在單獨的一個節,在初始化結束後就可以釋放這部分內存。

  

  * aligned (ALIGNMENT)

  


  屬性 aligned 用於變量、結構或聯合類型,指定變量、結構域、結構或聯合的對齊量,以字節為單位,例如:

  ++++ include/asm-i386/processor.h

  294: struct i387_fxsave_struct {

  295: unsigned shortcwd;

  296: unsigned shortswd;

  297: unsigned shorttwd;

  298: unsigned shortfop;

  299: long fip;

  300: long fcs;

  301: long foo;

  ......

  308: } __attribute__ ((aligned (16)));

  表示該結構類型的變量以 16 字節對齊。通常編譯器會選擇合適的對齊量,顯示指定對齊通常是由於體係限製、優化等原因。

  

  * packed

  


  屬性 packed 用於變量和類型,用於變量或結構域時表示使用最小可能的對齊,用於枚舉、結構或聯合類型時表示該類型使用最小的內存。例如:

  ++++ include/asm-i386/desc.h

  51: struct Xgt_desc_struct {

  52: unsigned short size;

  53: unsigned long address __attribute__((packed));

  54: };

  域 address 將緊接著 size 分配。屬性 packed 的用途大多是定義硬件相關的結構,使元素之間沒有因對齊而造成的空洞。

  

  當前函數名


  GNU CC 預定義了兩個標誌符保存當前函數的名字,__FUNCTION__ 保存函數在源碼中的名字__PRETTY_FUNCTION__ 保存帶語言特色的名字。在 C 函數中,這兩個名字是相同的,在 C++ 函數中,__PRETTY_FUNCTION__ 包括函數返回類型等額外信息,Linux 內核隻使用了 __FUNCTION__。

  ++++ fs/ext2/super.c

  98: void ext2_update_dynamic_rev(struct super_block *sb)

  99: {

  100: struct ext2_super_block *es = EXT2_SB(sb)->s_es;

  101:

  102: if (le32_to_cpu(es->s_rev_level) > EXT2_GOOD_OLD_REV)

  103: return;

  104:

  105: ext2_warning(sb, __FUNCTION__,

  106: "updating to rev %d because of new feature flag, "

  107: "running e2fsck is recommended",

  108: EXT2_DYNAMIC_REV);

  這裏 __FUNCTION__ 將被替換為字符串 "ext2_update_dynamic_rev"。雖然__FUNCTION__ 看起來類似於標準 C 中的 __FILE__,但實際上 __FUNCTION__是被編譯器替換的,不象 __FILE__ 被預處理器替換。

  

  內建函數

  


  GNU C 提供了大量的內建函數,其中很多是標準 C 庫函數的內建版本,例如memcpy,它們與對應的 C 庫函數功能相同,本文不討論這類函數,其他內建函數的名字通常以 __builtin 開始。

  

  * __builtin_return_address (LEVEL)


  內建函數 __builtin_return_address 返回當前函數或其調用者的返回地址,參數LEVEL 指定在棧上搜索框架的個數,0 表示當前函數的返回地址,1 表示當前函數的調用者的返回地址,依此類推。例如:

  ++++ kernel/sched.c

  437: printk(KERN_ERR "schedule_timeout: wrong timeout "

  438: "value %lx from %p/n", timeout,

  439: __builtin_return_address(0));

  * __builtin_constant_p(EXP)

  


  內建函數 __builtin_constant_p 用於判斷一個值是否為編譯時常數,如果參數EXP 的值是常數,函數返回 1,否則返回 0。例如:

  ++++ include/asm-i386/bitops.h

  249: #define test_bit(nr,addr) /

  250: (__builtin_constant_p(nr) ? /

  251:constant_test_bit((nr),(addr)) : /

  252:variable_test_bit((nr),(addr)))

  很多計算或操作在參數為常數時有更優化的實現,在 GNU C 中用上麵的方法可以根據參數是否為常數,隻編譯常數版本或非常數版本,這樣既不失通用性,又能在參數是常數時編譯出最優化的代碼。

  

  * __builtin_expect(EXP, C)

  


  內建函數 __builtin_expect 用於為編譯器提供分支預測信息,其返回值是整數表達式 EXP 的值,C 的值必須是編譯時常數。例如:

  ++++ include/linux/compiler.h

  13: #define likely(x) __builtin_expect((x),1)

  14: #define unlikely(x) __builtin_expect((x),0)

  ++++ kernel/sched.c

  564: if (unlikely(in_interrupt())) {

  565: printk("Scheduling in interrupt/n");

  566: BUG();

  567: }

  這個內建函數的語義是 EXP 的預期值是 C,編譯器可以根據這個信息適當地重排語句塊的順序,使程序在預期的情況下有更高的執行效率。上麵的例子表示處於中斷上下文是很少發生的,第 565-566 行的目標碼可能會放在較遠的位置,以保證經常執行的目標碼更緊湊。

(typeof獨說)

FROM    )
Another way to refer to the type of an expression is with typeof. The syntax of using of this keyword looks like sizeof, but the construct acts semantically like a type name defined with typedef.

There are two ways of writing the argument to typeof: with an expression or with a type. Here is an example with an expression:

     typeof (x[0](1))

This assumes that x is an array of pointers to functions; the type described is that of the values of the functions.

Here is an example with a typename as the argument:

     typeof (int *)

Here the type described is that of pointers to int.

If you are writing a header file that must work when included in ISO C programs, write __typeof__ instead of typeof. See Alternate Keywords.

A typeof-construct can be used anywhere a typedef name could be used. For example, you can use it in a declaration, in a cast, or inside of sizeof or typeof.

typeof is often useful in conjunction with the statements-within-expressions feature. Here is how the two together can be used to define a safe “maximum” macro that operates on any arithmetic type and evaluates each of its arguments exactly once:

     #define max(a,b) /
       ({ typeof (a) _a = (a); /
           typeof (b) _b = (b); /
         _a > _b ? _a : _b; })

The reason for using names that start with underscores for the local variables is to avoid conflicts with variable names that occur within the expressions that are substituted for a and b. Eventually we hope to design a new form of declaration syntax that allows you to declare variables whose scopes start only after their initializers; this will be a more reliable way to prevent such conflicts.

Some more examples of the use of typeof:

This declares y with the type of what x points to.
          typeof (*x) y;
    
This declares y as an array of such values.
          typeof (*x) y[4];
    
This declares y as an array of pointers to characters:
          typeof (typeof (char *)[4]) y;
    
It is equivalent to the following traditional C declaration:

          char *y[4];
    
To see the meaning of the declaration using typeof, and why it might be a useful way to write, rewrite it with these macros:

          #define pointer(T)  typeof(T *)
          #define array(T, N) typeof(T [N])
    
Now the declaration can be rewritten this way:

          array (pointer (char), 4) y;
    
Thus, array (pointer (char), 4) is the type of arrays of 4 pointers to char.

Compatibility Note: In addition to typeof, GCC 2 supported a more limited extension which permitted one to write

     typedef T = expr;

with the effect of declaring T to have the type of the expression expr. This extension does not work with GCC 3 (versions between 3.0 and 3.2 will crash; 3.2.1 and later give an error). Code which relies on it should be rewritten to use typeof:

     typedef typeof(expr) T;

This will work with all versions of GCC。

最後更新:2017-04-02 00:06:41

  上一篇:go CSDN的自動發布時間認證太低級了
  下一篇:go 鏈式隊列(數據結構C#)