IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    [转]GNU C 与 ANSI C的区别

    lincyang发表于 2013-11-07 09:37:14
    love 0

    网上找来如下两篇笔记,希望这个能帮助自己和读者。

    一。

    从人民邮电的《Linux设备驱动开发详解》上抄的

    华清远见的宋老师著,可惜咱没钱上他的课 百十来块钱的书 我还是从来不吝啬的

    可是 华清远见也不能出那么多书啊 本来想全买的,现在发现买的速度比不上他们出书的速度

    看了这一节感觉C语言的见识又大大增强

    发现了书中一处笔误,查资料时又发现了一些结构体对齐上别人的错误,^_^ 真有点飘飘然了

    这篇的东西只对GNU C有效,其他的标准C 或者VC 并不适用

    1.允许零长度数组

    GNU C允许零长度数组,在定义变长对象的头结构时,这个特性非常有用。

    struct var_data s

    {

          int len;

          char data[0];

    };

    char data[0]仅仅意味着程序中通过var_data的结构体实例的data[index]成员可以访问len之后的第index个地址,并没有为data[0]分配内存。

    假设struct var_data的数据域保存在struct var_data紧接着的内存区域,通过如下代码可以遍历这些数据:

    struct var_data s;

    ...

    for (i=0;i<s.len;i++)

    {

        printf("%02x",s.data[i]);

    }

    2、case范围

    GNU C 支持case x...y 这样的语法,区间[x,y]的数都会满足这个case的条件,记得数据结构试验时,有的同学为了做菜单使用了仅100个case,还好我做的是GUI的

    switch(c)

    {

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

          break;

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

          break;

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

          break;

    }

    这个case的特点大家都看得出来,比标准C少敲了多少case啊

    3、语句表达式

    GNU C把包含在括号里的复合语句看做是一个表达式,称为语句表达式,它可以出现在任何允许表达式的地方。我们可以在语句表达式中使用原本只能在复合语句中使用的循环变量、局部变量等,例如

    #define min_t(type,x,y) \

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

    int ia,ib,mini;

    mini=min_t(int,ia,ib);

    这样,因为重新定义了__x和__y这两个局部变量,所以上述方法定义的宏将不会有副作用。在标准C中,对应的宏通常会有副作用:

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

    而代码min(++ia,++ib)将会被展开为

    ((++ia)<(++ib)?(++ia):(++ib)) 传入宏的参数会被增加两次。

    这个在 嵌入式程序员应知道的0x10个基本问题 里有讲过。

    4、typeof关键字

    typeof(x)语句可以获得x的类型,因此,我们可以借助typeof重新定义第3条提到的min_t这个宏

    #define min(x,y) \

    ({ \

          const typeof(x) _x=(x);\

          const typeof(y) _y=(y);\

          (void) (&_x==&_y);\

           _x<_y ? _x: _y ; })

    我们不需要像第三条时那样传一个type进去,因为通过typeof(x)可以得到type。

    代码 (void) (&_x==&_y);的作用是检查_x和_y的类型是否一致。

    5、可变参数的宏

    标准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",);

    6.标号元素

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

    指定数组索引的方法是在初始化值前添加 [INDEX]= ,当然也可以用 [FIRST...LAST]= 的形式指定一个范围。例如下面的代码定义一个数组,并把其中的所有元素赋值为0:

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

    下面的代码借助结构体成员名初始化结构体:

    struct file_operations DEMO_fops = {
        owner :    THIS_MODULE,
        llseek:      DEMO_llseek,
        read:       DEMO_read,
        write:       DEMO_write,
        ioctl:        DEMO_ioctl,
        open:        DEMO_open,
        release:   DEMO_release,
    };

    但是Linux 2.6还是推荐采用标准C的方式,如下

    struct file_operations DEMO_fops = {
        .owner =    THIS_MODULE,
        .llseek =   DEMO_llseek,
        .read =     DEMO_read,
        .write =    DEMO_write,
        .ioctl =    DEMO_ioctl,
        .open =     DEMO_open,
        .release = DEMO_release,
    };

    7.当前函数名

    GUN C预定义了两个标识符保存当前的函数名,__FUNCTION__保存函数在源码中的名字,

    __PRETTY_FUNCTION__保存带语言特色的名字。在c函数中,这两个名字是相同的。

    void example()

    {

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

    }

    代码中的__FUNCTION__意味着字符串"example"

    8、特殊属性声明

    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)));

    详细的可以看http://blog.163.com/sunm_lin/blog/static/9192142200741533038695/

    unused属性作用于函数和变量,表示该函数或变量可能不会被用到,避免编译器产生的警告信息。

    aligned属性指定结构体、变量、联合体的对齐方式。packed属性作用于变量和类型,表示压缩结构体,使用最小的内存。

    struct examprl_struct

    {

          char a;

          int b;

          long c;

    }__attribute__((packed));

    注意,这个__attribute__((packed))只能用在GNU C

    关于在VC下的结构体对齐,参照http://hi.baidu.com/deep_pro/blog/item/421db081aeb604debd3e1e01.html

    9、内建函数

    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 行的目标码可能会放在较远的位置,以保证经常执行的目标码更紧凑。

    二。

    1.零长度数组

    GNU C允许使用零长度数组,定义变长度对象时比较方便

    struct var_data {

        int len;

        char data[0];

    };

    var_data的大小仅为一个int型,data是常量地址,data[index]是访问其后的内存空间。

    struct var_data *s = malloc(sizeof(var_data) + len);

    释放内存的时候free(s)只释放int,所以还要认为地释放data空间,这会带来不便。另外只有GNU C支持,c/c++编译永远通不过。

    2.case范围

    GNU C支持case x...y这样的语法,区间[x ,y]都满足这个条件。例如

    case 0...5 ==> case 0 : case 1: case 2: case 3: case 4: case 5: 

    3.语句表达式

    GNU C可以把括号中的复合语句看成是语句表达式, a=( ; ; ),于是就有了以下应用

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

    float minf  = min_t(float, f1, f2);

    int mini  = min_t(int, i1, i2);

    4.typeof关键字

    typeof(x)语句可以获得x的类型

    5.可变参数的宏

    标准C只支持可变参数的函数

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

    而GNU C也支持可变参数的宏

    #define pr_debug(fmt, arg...)   printfk(fmt, ##arg)

    6.标号元素

    标准C要求数组或结构体的初始值必须以固定顺序出现,而GNU C可以通过指定索引和结构体成员允许初始化值以任意顺序出现([index] = ),当然也可以如下运用

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

    下面是借助结构体成员名初始化值

    struct file_operations ext2_file_operation = {

    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,

    };

    但是linux2.6推荐类似的代码应该尽量采用标准C的语法

    struct file_operations ext2_file_operation = {

    .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,

    };

    7.当前函数名

    GNU C预定义了两个标识符保存当前函数的名字,__FUNCTION__保存函数在源码中的名字,__PRETTY_FUNCTION__保存带语言特色的名字,而标注C两者是一样的。

    void exampe()

    {

        printf("This is function %s\n", __FUNCTION__);

    }

    8.特殊属性声明

    GNU C允许声明函数,变量和类型的特殊属性,以便进行手工优化和定制代码检查的方法。指定一个属性只需在其声明后添加__attribute__((ATTRIBUTE)).

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

    例如:void do_exit(int n) __attribute__((noreturn));

    format属性也用于函数,该函数使用printf、scanf或strftime风格的参数,指定format属性可以让编译器根据格式串检查参数类型。

    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));

    } ;

    9.内建函数

    GNU C除了标准C提供的内建函数(memcpy)外,还提供了许多其他的内建函数,通常命名以__builtin开始。

    __builtin_return_address(LEVEL)返回当前函数或调用这的返回地址,参数LEVEL指定调用栈的级数,如0表示当前函数的返回地址,1表示当前函数的调用者的返回地址。

    __builtin_constant_p(EXP)用于判断一个值是否为编译时常数,是返回1,否则返回0.例如下面的代码检测第一个参数是否为常数以确定采用参数版本还是非参数版本

    #define test_bit(nr, addr) (__builtin_constant_p(nr) ? constant_test_bit((nr), (addr)) : variable_test_bit((nr), (addr))

    ——记《linux设备驱动开发详解》宋宝华


    Linc按

    对结构体对齐的一些补充:

    //2013.11.17
    //Linc
    
    #include <stdio.h>
    
    struct test1 {
    	char a;
    	short b;
    	int c;
    };
    
    struct test2 {
    	char a;
    	short b;
    	char c;
    };	
    
    struct test3 {
    	char a;
    	int b;
    	char c;
    } __attribute__((packed)) scsi ;	
    
    main()
    {
    	struct test1 test;
    	printf("the test1 size is %d\n",sizeof(test));
    
    	int i;
    	printf("the int size is %d\n",sizeof(i));
    
    	char str;
    	printf("the char size is %d\n",sizeof(str));
    
    	short s;
    	printf("the short size is %d\n",sizeof(s));
    	
    	struct test2 test_2;
    	printf("the test2 size is %d\n",sizeof(test_2));
    
    	struct test3 test_3;
    	printf("test3 size is %d\n",sizeof(test_3));
    
    }
    
    结果如下:

    [linc@localhost test-struct]$ ./test 
    the test1 size is 8
    the int size is 4
    the char size is 1
    the short size is 2
    the test2 size is 6
    test3 size is 6
    

    test1的长度不是12而是8,请各路大侠指教!




沪ICP备19023445号-2号
友情链接