2.2.2 GNU C对标准C语言的扩展

来源:互联网 发布:java程序的开始 编辑:程序博客网 时间:2024/05/17 01:02

为了方便我们的使用,GNU C在标准C语言的基础上进行了部分方便开发的扩展。这里讲解一些我们开发中可能会用到的,或者使用频率比较高的内容。

  1. 零长度数组和变量长度数组

    GNU C 允许使用零长度数组,比如:

    char data[0];  

    GNU C 允许使用一个变量定义数组的长度如:

    int n=0;scanf("%d",&n);int array[n];  
  2. case 范围

    GNU C支持 case x…y这样的语法,[x,y]之间数均满足条件。

    case 'a'...'z':  /*from 'a' to 'z'*/break;  
  3. 语句表达式
    GNU C 把包含在括号中的复合语句看作是一个表达式,称为语句表达式。

     #define min_t(type,x,y)\         ({type __x=(x); type __y=(y);__x<__y?__x:__y;})

    这种写法可以避免

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

    在min_t(x++,++y)中出现的副作用

  4. typeof 关键字

    typeof(x)可以获得x的类型借助typeof关键字我们可以重新定义min_t:

    #define min_t(x,y)\    ({typeof(x) __x=(x); typeof(y) __y=(y);__x<__y?__x:__y;})  
  5. 可变参数宏

    GNU C中宏也支持可变参数

    #define pr_debug(fmt,arg...) \        printk(fmt,##arg)  

    这里,如果可变参数被忽略或为空,“##”操作将使预处理器去掉它前面的那个逗号。如果你在宏调用时,确实提供了一些可变参数,GNU C也会工作正常,它会把这些可变参数放到逗号的后面。

  6. 标号元素

    标准C要求数组或结构体的初始化值必须以固定的顺序出现,在GNU C中,通过指定索引或结构体成员名,允许初始化以任意顺序出现。

    unsigned char data[MAX] ={         [0]=10,         [10]=100,};struct file_operations ext2_file_operations={        open:ext2_open,        close:ext2_close,};

    在linux 2.6中推荐如下方式:

    struct file_operations ext2_file_operations={     .read=ext2_read,     .write=ext2_write,};  
  7. 当前函数名

    GNU C中预定义两个标志符保存当前函数的名字,_ FUNCTION _ 保存函数在源码中的名字, _ PRETTY_ FUNCTION __保存带语言特色的名字。在C函数中这两个名字是相同的.

    void func_example(){     printf("the function name is %s",__FUNCTION__);}

    在C99中支持_ func _ 宏,因此建议使用 _ func _ 替代 _ FUNCTION _

  8. 特殊属性声明

    GNU C 允许声明函数、变量和类型的特殊属性,以便进行手工的代码优化和定制。如果要指定一个属性声明,只需要在声明后添加_ attribute _((ATTRIBUTE))。其中ATTRIBUTE为属性说明,如果存在多个属性,则以逗号分隔。GNU C 支持noreturn,noinline, always_inline, pure, const, nothrow, format, format_arg, no_instrument_function, section, constructor, destructor, used, unused, deprecated, weak, malloc, alias warn_unused_result nonnull等十个属性。

    noreturn属性作用于函数,表示该函数从不返回。这会让编译器优化代码并消除不必要的警告信息。例如:

    #define ATTRIB_NORET __attribute__((noreturn)) ....asmlinkage NORET_TYPE void do_exit(long error_code) ATTRIB_NORET;  

    packed属性作用于变量和类型,用于变量或结构域时,表示使用最小可能的对齐,用于枚举、结构或联合类型时表示该类型使用最小的内存。如对于结构体,就是它告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。例如:

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

    regparm属性用于指定最多可以使用n个寄存器(eax, edx, ecx)传递参数,n的范围是0~3,超过n时则将参数压入栈中(n=0表示不用寄存器传递参数)。

    注意:以上这些属性都是在X86处理器体系结构下的,在X64体系结构下,大部分内容都是同样有效的。但是,这里要注意regparm属性,由于在X64体系结构下,GUN C的默认调用约定使用寄存器传递参数。所以,如果regparm属性里使用的寄存器个数超过3个,也仍然会使用其他寄存器来传递参数。这一点要遵循X64体系结构的调用约定。

    下面可以看一个例子。

    int q = 0x5a;int t1 = 1;int t2 = 2;int t3 = 3;int t4 = 4;#define REGPARM3 __attribute((regparm(3)))#define REGPARM0 __attribute((regparm(0)))void REGPARM0 p1(int a){     q = a + 1;}void REGPARM3 p2(int a, int b, int c, int d){     q = a + b + c + d + 1;}int main(){    p1(t1);    p2(t1,t2,t3,t4);    return 0;}  

    使用objdump命令反汇编,相关命令如下:

    objdump -D 可执行程序  

    其中-D选项用于反汇编所有的程序段,包括:代码段、数据段、只读数据段以及一些系统段等等。而-d命令只反汇编代码段的内容。

    反汇编后的关键代码如下:

    Disassembly of section .text:0000000000400474 <p1>:  400474:   55                      push   %rbp  400475:   48 89 e5                mov    %rsp,%rbp  400478:   89 7d fc                mov    %edi,-0x4(%rbp)  40047b:   8b 45 fc                mov    -0x4(%rbp),%eax  40047e:   83 c0 01                add    $0x1,%eax  400481:   89 05 3d 04 20 00       mov    %eax,0x20043d(%rip)        # 6008c4 <q>  400487:   c9                      leaveq   400488:   c3                      retq   0000000000400489 <p2>:  400489:   55                      push   %rbp  40048a:   48 89 e5                mov    %rsp,%rbp  40048d:   89 7d fc                mov    %edi,-0x4(%rbp)  400490:   89 75 f8                mov    %esi,-0x8(%rbp)  400493:   89 55 f4                mov    %edx,-0xc(%rbp)  400496:   89 4d f0                mov    %ecx,-0x10(%rbp)  400499:   8b 45 f8                mov    -0x8(%rbp),%eax  40049c:   8b 55 fc                mov    -0x4(%rbp),%edx  40049f:   8d 04 02                lea    (%rdx,%rax,1),%eax  4004a2:   03 45 f4                add    -0xc(%rbp),%eax  4004a5:   03 45 f0                add    -0x10(%rbp),%eax  4004a8:   83 c0 01                add    $0x1,%eax  4004ab:   89 05 13 04 20 00       mov    %eax,0x200413(%rip)        # 6008c4 <q>  4004b1:   c9                      leaveq   4004b2:   c3                      retq   00000000004004b3 <main>:  4004b3:   55                      push   %rbp  4004b4:   48 89 e5                mov    %rsp,%rbp  4004b7:   53                      push   %rbx  4004b8:   8b 05 0a 04 20 00       mov    0x20040a(%rip),%eax        # 6008c8 <t1>  4004be:   89 c7                   mov    %eax,%edi  4004c0:   e8 af ff ff ff          callq  400474 <p1>  4004c5:   8b 0d 09 04 20 00       mov    0x200409(%rip),%ecx        # 6008d4 <t4>  4004cb:   8b 15 ff 03 20 00       mov    0x2003ff(%rip),%edx        # 6008d0 <t3>  4004d1:   8b 1d f5 03 20 00       mov    0x2003f5(%rip),%ebx        # 6008cc <t2>  4004d7:   8b 05 eb 03 20 00       mov    0x2003eb(%rip),%eax        # 6008c8 <t1>  4004dd:   89 de                   mov    %ebx,%esi  4004df:   89 c7                   mov    %eax,%edi  4004e1:   e8 a3 ff ff ff          callq  400489 <p2>  4004e6:   b8 00 00 00 00          mov    $0x0,%eax  4004eb:   5b                      pop    %rbx  4004ec:   c9                      leaveq   4004ed:   c3                      retq     4004ee:   90                      nop  4004ef:   90                      nopDisassembly of section .data:00000000006008c0 <__data_start>:  6008c0:   00 00                   add    %al,(%rax)    ...00000000006008c4 <q>:  6008c4:   5a                      pop    %rdx  6008c5:   00 00                   add    %al,(%rax)    ...00000000006008c8 <t1>:  6008c8:   01 00                   add    %eax,(%rax)    ...00000000006008cc <t2>:  6008cc:   02 00                   add    (%rax),%al    ...00000000006008d0 <t3>:  6008d0:   03 00                   add    (%rax),%eax    ...00000000006008d4 <t4>:  6008d4:   04 00                   add    $0x0,%al    ...

    如果读者还记得2.1.3节中,关于GCC基于X64体系结构的调用约定的话,那就很容易可以看出,函数p1和p2都使用寄存器传递参数,顺序就是RDI, RSI, RDX, RCX,这些细节已经跟regparm的规定完全不一致了。所以,在这里作者觉得,regparm已经不起作用了。

本文已在图灵出版社社区连载中,欢迎大家前去阅读。

网址:http://www.ituring.com.cn/minibook/29303

0 0
原创粉丝点击