zoukankan      html  css  js  c++  java
  • GNU C 扩展(转)

    GNU CC 是一个功能非常强大的跨平台 C 编译器,它对 C 语言提供了很多扩展,这些扩展对优化、目标代码布局、更安全的检查等方面提供了很强的支持。这里对支持支持 GNU 扩展的 C 语言成为 GNU C。 在 Linux 内核中使用了大量的 GNU C 扩展,以致 GNU C 成为了内核唯一的编译器。


    1、语句表达式 

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

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

      (针对下边的源码)预编译结果:j=({int __x=(*p++); int __y=(i); __x<__y? __x:__y});
    而标准的宏定义为:

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

      (针对下边的源码)预编译结果:j=( (*p++)<(i)? (*p++):(i));
    这个定义计算 x 和 y 分别两次,当参数有副作用时,将产生不正确的结果,见:http://www.groad.net/bbs/read.php?tid=1034


    修改上面链接代码如下

    引用
    #include <stdio.h>

    #define MIN(type, x, y) ({ type __x = (x); type __y = (y); __x < __y ? __x: __y;})
    int main ()
    {
        int i = 10;
        int a[5] = {8,9,11,10,13};

        int *p = a;
        int j;
        
        j = MIN(int, *p++, i);
        printf("%d ", j);
        
        printf("%d ", *p);

        return (0);
    }


    运行及输出

    引用
    beyes@linux-beyes:~/C/micro> ./micro2.exe 
    8
    9


    说明
    这时候,*P 不再做 2 次的 + 1 运算。这是因为宏定义的语句表达式中有赋值语句,此后作用的是 __x (为 8)而不是 x (为 *p++)。


    上面的宏定义,需要提供参数类型,如 int 。但是如果用 typeof  就可以定义更加通用的宏。

    测试代码

    引用
    #include <stdio.h>

    #define MIN(x, y) ({ const typeof(x) _x = (x); const typeof(y) _y = (y); _x < _y ? _x:_y;})

    int main ()
    {
            int i = 10;
            float a[5] = {8.1,9.1,11.2,10.3,13.4};

            float  *p = a;
            float j;

            j = MIN(*p++, i);
            printf("%f ", j);

            printf("%f ", *p);

            return (0);
    }


    运行及输出

    引用
    beyes@linux-beyes:~/C/micro> ./micro3.exe 
    8.100000
    9.100000


    说明
    应用上面程序中的宏,不再需要额外提供一个参数的类型。和上面的例子一样,不会产生副作用,最终用来比较的值是所希望的数组中的第一个元素 8.1。注意,在宏执行完后,指针还是要移动到数组的下一个元素。

    beyes 2009-08-14 02:24
    GNU C 允许使用零长度数组,在定义变长的头结构时,这个特性非常有用。

    测试代码
    引用
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>

    typedef struct user_def {
            char *name;
            int  length;
            char bytes[0];
    } user_def_t;

    int main()
    {
            int length = 10;
            user_def_t *p;

            p = (user_def_t *)malloc (sizeof(user_def_t) + length);

            if (p == NULL) {
                    printf("malloc failed ");
                    exit(1);
            }
            p->name = "good";
            p->length = length;

            memset(p->bytes, 0, length);
            p->bytes[0] = 'a';
            p->bytes[1] = 'b';
            p->bytes[2] = 'c';

            printf("%s ", p->bytes);

            free(p);
            return 0;
    }


    运行及输出
    引用
    beyes@linux-beyes:~/C/GNU_C_EXT> ./entry.exe 
    abc

    说明
    如果用 sizeof  结构体中的 bytes 大小,那么得到的是 0 ,所以它是一个零长度数组。但是,零长度数组的作用不是用来定义一个结束地址,而是为了将来的扩展。结构体本身类似于一个信息头。同时,此结构只能通过堆方式分配内存。注意的是,结构体中的数组不是非得是零长度数组才,如果定义了长度也是可以的,但是如果基于将来进行扩展的目的,这就显得没有必要了。

     

    beyes 2009-08-14 12:18
    在 GNU C 中,宏可以接受可变数目的参数,就像函数一样,如:
    引用
    #define pr_debug(fmt, arg...)
    printk(KERN_DEBUG fmt, ##arg);


    以前可变参数只能应用在真正的函数中,不能用在宏里。但在 C99 编译器标准中,它允许定义可变参数宏(variadic macros),这样就可以拥有可以变化的参数表的宏。比如:
    引用
    #define debug(...) printf(__VA_ARGS__)

    debug 中的省略号表示一个可以变化的参数列表。__VA_ARGS__ 是个保留名,它把参数传递给宏。当宏展开时,实际的参数就传递给了 printf() 。完整的测试代码如下:
    引用
    #include <stdio.h>

    #define debug(...) printf(__VA_ARGS__)

    int main()
    {
        char a[20] = "hello world ";
        int i = 10;
        debug("i = %d, %s", i, a);    //此句经预编译后:printf("i=%d,%s",i,a);

        return 0;
    }
     
    运行及输出
    引用
    beyes@linux-beyes:~/C/micro> ./mic.exe
    i = 10, hello world

    由于 debug() 是个可变参数宏,所以在每次调用中能给它传递不同数目的参数。
    注意,可变参数宏不被 ANSI/ISO C++ 所正式支持。因此,在使用这项功能时,要检查边起义的版本是否对其支持。


    GCC 支持复杂的宏,它使用一种不同的语法,使你可以给可变参数一个名字,如同其它参数一样,比如:
    引用
    #define debug(format, args...) fprintf(stderr, format, args)

    这种定义可读性更强,也更容易描述。完整测试代码:
    引用
    #include <stdio.h>

    #define debug(format, args...) fprintf(stderr, format, args)

    int main()
    {
        char a[20] = "hello world ";
        int i = 10;
        debug("i = %d, %s", i, a);

        return 0;
    }

    运行输出
    引用
    beyes@linux-beyes:~/C/micro> ./mic.exe 
    i = 10, hello world

    但是上面的定义仍存在一点问题,如果把上面的代码改为下面的:
    引用
    #include <stdio.h>

    #define debug(format, args...) fprintf(stderr, format, args)

    int main()
    {

        debug("hello world ");   //预编译结果:  fprintf(stderr, "hello C ",);  //编译后有,但是没有参数,会导致出错

        return 0;
    }

    那么在编译时会提示以下错误
    引用
    beyes@linux-beyes:~/C/micro> gcc -g mic.c -o mic.exe
    mic.c: In function ‘main’:
    mic.c:10: error: expected expression before ‘)’ token

    提示缺少右边括号。这是因为,当宏展开后,"hello world " 代入 format,然而,在其后还紧跟着一个逗号,但是这个逗号后面是期望有 args 参数的,但这里却没有,所以宏不能展开完全,故而无法编译通过。那么,再改一下宏定义:
    引用
    #include <stdio.h>

    #define debug(format, args...) fprintf(stderr, format, ##args)

    int main()
    {
        debug("hello world ");

        return 0;
    }

    这时候,再编译运行及输出:
    引用
    beyes@linux-beyes:~/C/micro> gcc -g mic.c -o mic.exe
    beyes@linux-beyes:~/C/micro> ./mic.exe
    hello world

    编译通过,并正常输出。上面的代码,在 fprintf() 中的 args 前面加了两个 # 号 ##。
    ## 号的作用
    如果可变参数部分( args...) 被忽略或为空,那么 "##" 操作会使预处理器 (preprocessor) 去掉它前面的那个逗号。如果在调用宏时,确实提供了一些可变参数,GNU C 也会正常工作,它会把这些可变参数放在逗号的后面;如果没有提供,它就会自动去掉前面的逗号,使宏结束展开 ---- 补充完右边括号。

    另外,假如按照 C99 的定义来用,改宏为:
    引用
    #define debug(format, args...) fprintf(stderr, format, ##__VA_ARGS__)

    那么编译会出错:
    引用
    beyes@linux-beyes:~/C/micro> gcc -g mic.c -o mic.exe
    mic.c:3:58: warning: __VA_ARGS__ can only appear in the expansion of a C99 variadic macro
    mic.c:9:1: error: pasting "," and "__VA_ARGS__" does not give a valid preprocessing token
    mic.c: In function ‘main’:
    mic.c:9: error: ‘__VA_ARGS__’ undeclared (first use in this function)
    mic.c:9: error: (Each undeclared identifier is reported only once
    mic.c:9: error: for each function it appears in.)

    原因在于,args... 和 ##__VA_ARGS__ 是不匹配的,正确的匹配应该是:
    引用
    #define debug(format, ...) fprintf(stderr, format, ##__VA_ARGS__)

    注意,... 省略号对应的就是 __VA_ARGS__

    一般的,定义可变参数宏的一个流行方法,形如:
    引用
    #define DEBUG(args) (printf("DEBUG: "), printf args)
    if(n != 0) DEBUG(("n is %d ", n));

    这个方法的一个缺点是,要记住一对额外的括弧

     

    beyes 2009-08-14 22:53
    标准 C 要求数组或结构变量的初始化值必须以固定的顺序出现。比如初始化一个数组: char a [5] = {'a', 'b','c'}; 则必定是 a[0]  为 a; a[1] 为 b; a[2] 为 c ,这是一个固定的初始化顺序。

    在 GNU C 中,通过指定索引,允许初始值以任意的顺序出现。下面是一个示例代码:
    [font=[object htmloptionelement]]
    引用
    #include <stdio.h>

    #define SIZE 10
    int main()
    {
        unsigned long array[SIZE] = {[2 ... SIZE-1] = 8};
        int i;
        
        for (i = 0; i < 10; i++)
            printf("%d  ", array [i]);

        printf(" ");

        return 0;
    }
          

    运行与输出
    引用
    beyes@linux-beyes:~/C/GNU_C_EXT> ./sign.exe 
    0  0  8  8  8  8  8  8  8  8 

    说明:
    从程序中可以看到,可以从第 2 个元素初始化到最后一个元素,初始化值都是 8 ,而第0,第1个元素被默认初始化,值为0。

     

    beyes 2009-08-15 12:21
    GNU C 允许在一个 case 标号中指定一个连续范围的值

    测试代码
    引用
    #include <stdio.h>

    void test (char code)
    {
        switch (code) {
            case '0' ... '9':
                printf("code in 0~9 ");
                break;
            case 'a' ... 'f':
                printf("code in a~f ");
                break;
            case 'A' ... 'F':
                printf("code in A~F ");
                break;
            default:
                printf("no right code ");
        }
    }    

    int main()
    {
        
        test('9');
        test('f');
        test('z');
        test('C');

        return (0);
    }

    运行输出
    引用
    beyes@linux-beyes:~/C/GNU_C_EXT> ./case_more.exe 
    code in 0~9
    code in a~f
    no right code
    code in A~F

     

    beyes 2009-08-15 15:03
    GNU C 预定义了两个标志符保存当前函数的名字,__FUNCTION__ 保存函数在源码中的名字,__PRETTY_FUNCTION__ 保存带语言特色的名字。在 C 函数中,这两个名字是相同的,在 C++ 函数中,__PRETTY_FUNCTION__ 包括函数返回类型等额外信息,Linux 内核只使用了 __FUNCTION__。 



    通常,在调式中最让人烦恼的阶段是不断地检查是否已调用了特定的函数。对此问题,一个解决办法是,在函数里添加 printf() <针对 C 语言>,如:
    引用
    void func_test ()
    {
       printf(" func_test() ");
       /* 其他代码 */
    }

    但是,通常在一个典型的工程中,会包含有数千个函数,如果在每个函数中都加入一条这样的语句,那将非常痛苦。所以,现在有一种机制,可以自动玩成这项工作: __FUNCTION__


    在最新的 ISO C 标准中,如 C99,加入了另一个有用的,类似于宏的表达式 __func__ ,它会报告未修饰过的(也就是未裁减过的)、正在被访问的函数名。注意,__func__ 不是一个宏,因为预处理器对此函数一无所知;相反,它是作为一个隐式声明的常量字符数组实现的:
    static const char __func__[] = "functon-name";
    在 function-name 处,为实际的函数名。


    测试代码
    引用
    #include <stdio.h>

    void show_name (const char *name)
    {
        printf("%s ", name);
    }

    void fun_test ()
    {
        show_name (__FUNCTION__);
        printf (" ");
    }

    void fun_test2 ()
    {
        printf (__func__);
        printf (" ");
    }

    int main()
    {
        fun_test();
        fun_test2();
        return 0;
    }

    运行及输出
    引用
    beyes@linux-beyes:~/C/GNU_C_EXT> ./FUNCTION.exe 
    fun_test

    fun_test2

    说明
    __func__ 标识符为官方 C99 标准定义,但是 ISO C++ 却不完全支持所有的 C99 扩展。因此,大多数编译器提供商都使用 __FUNCTION__ 取而代之。__FUNCTION__ 通常是一个定义为 __func__ 的宏,之所以使用这个名字,是因为它已经得到了广泛的支持。

     

    beyes 2009-08-15 17:25
    GNU C 提供了大量的内建函数,其中很多是标准 C 库的内建版本,例如 memcpy(),它们与对应的 C 库函数功能相同。而其他内建的名字通常以 __builtin 开始
    • __builtin_return_address (LEVEL)
    内建函数 __builtin_return_address 返回当前函数或其调用者的返回地址,参数 LEVEL 指定在栈上搜索框架的个数,0 表示当前函数的返回地址,1 表示当前函数的调用者的返回地址,依次类推。

    下面是测试代码
    引用
    #include <stdio.h>

    int *address;
    int *builtin_func ()
    {
        address = __builtin_return_address(0);
        return address;
    }

    int main()
    {

        builtin_func();

        printf("%p ", address);
        
        return (0);
    }

    运行及输出
    引用
    beyes@linux-beyes:~/C/GNU_C_EXT> ./builtin.exe 
    0x804844c


    看一下 builtin.exe 的反汇编代码
    引用
    08048436 <main>:
     8048436:    8d 4c 24 04              lea    0x4(%esp),%ecx
     804843a:    83 e4 f0                 and    $0xfffffff0,%esp
     804843d:    ff 71 fc                 pushl  -0x4(%ecx)
     8048440:    55                       push   %ebp
     8048441:    89 e5                    mov    %esp,%ebp
     8048443:    51                       push   %ecx
     8048444:    83 ec 14                 sub    $0x14,%esp
     8048447:    e8 d8 ff ff ff           call   8048424 <builtin_func>
     804844c:    a1 1c a0 04 08           mov    0x804a01c,%eax
     8048451:    89 44 24 04              mov    %eax,0x4(%esp)
     8048455:    c7 04 24 30 85 04 08     movl   $0x8048530,(%esp)
     804845c:    e8 f3 fe ff ff           call   8048354 <printf@plt>
     8048461:    b8 00 00 00 00           mov    $0x0,%eax
     8048466:    83 c4 14                 add    $0x14,%esp
     8048469:    59                       pop    %ecx
     804846a:    5d                       pop    %ebp
     804846b:    8d 61 fc                 lea    -0x4(%ecx),%esp
     804846e:    c3                       ret    
     804846f:    90                       nop   


    • __builtin_constant_p (EXP)
    内建函数 __builtin_constant_p 用于判断一个值是否为编译时的常数,如果参数 EXP 的值是常数,函数返回 1,否则返回 0 。

    测试代码
    引用
    #include <stdio.h>

    #define SIZE 100

    int main()
    {    
        int k;
        k = __builtin_constant_p (SIZE);    
        
        if (k == 1) {
            printf("SIZE is constant ");
            return 0;
        } else {
            printf("SIZE is not constant ");
            return 0;
        }

        return 0;
    }

    运行及输出
    引用
    beyes@linux-beyes:~/C/GNU_C_EXT> ./builtin_const.exe 
    SIZE is constant


    • __builtin_expect (EXP,C)
    内建函数 __builtin_expect  用于为编译器提供分支预测信息,其返回值是整数表达式 EXP 的值,C 的值必须是编译时的常数。 //?

    测试程序
    引用
    #include <stdio.h>

    #define TEST 10
    #define TST  16

    int expect (int a, int b)
    {
            return (a + b);
    }



    int main()
    {
            int a = 8;
            int b = 2;

            if ( __builtin_expect((expect(a, b)), TEST)) {
                    printf ("expected TEST ");
                    return 0;
            }

            b = 8;
            if ( __builtin_expect((expect(a, b)), TST)) {
                    printf ("expected TST ");
                    return 0;
            }

            printf ("none expected ");
            return 0;
    }

    运行与输出
    引用
    beyes@linux-beyes:~/C/GNU_C_EXT> ./builtin_const.exe 
    SIZE is constant

    这个内建函数的语义是 EXP 的预期值是 C,编译器可以根据这个信息适当地重排语句块的顺序,使程序在预期的情况下有更高的执行效率。

     

    beyes 2009-08-16 12:38

    在 http://www.groad.net/bbs/read.php?tid=1032 中转载了一篇关于 __attribute__ 机制说明的文章,这里再用完整的一般测试代码进行测试,作为加深理解与补充。

    __attribute__属性参考:http://www.cnblogs.com/kwseeker-bolgs/p/4396383.html

    1、__attribute__ format 属性


    语法格式
    format (archetype, string-index, first-to-check)

    参数说明
    archtype  :  指定是哪种风格 ();
    string-index : 指定传入的第几个参数是格式化字符串;
    first-to-check : 指定从函数的第几个参数开始检查上述规则。

    具体格式
    __attribute__((format(printf,m,n)));
    __attribute__((format(scanf,m,n)));

    测试代码

    引用
    #include <stdio.h>
    #include <stdarg.h>

    void self_printf(const char *format, ...) __attribute__ ((format(printf,1,2)));

    int main()
    {
            int a = 10;
            int b = 8;
            int c;
            char buf [20] = "hello world";

            c = 10;
            self_printf("%d %d %s ", a, b, buf);
            return 0;
    }

    void self_printf(const char *format, ...)
    {
            int i;
            char c, *p;
            va_list ap;

            va_start (ap, format);

            while (*format)
                    switch (*format++) {
                            case 's':
                               p = va_arg (ap, char *);
                               printf ("%s", p);
                               break;

                            case 'd':
                               i = va_arg (ap, int);
                               printf ("%d ", i);
                               break;

                            case 'c':
                                c = (char)va_arg (ap, int);
                                printf ("%c ", c);
                                break;

                            case ' ':
                                c = (char)va_arg (ap, int);
                                printf(" ");
                                break;
                    }

            va_end (ap);
    }

    运行及输出
    引用
    beyes@linux-beyes:~/C/GNU_C_EXT> ./attribute.exe 
    10 8 hello world

    说明
    self_printf() 中,第 1 个参数是 const char *format,即格式化字符串;第 2 个参数是省略号 "...",这也就是参数个数不定的参数列表。

    在 __attribute__ ((format(printf,1,2))); 里,1 就是表示 const char *format 格式化字符串; 2 表示从第二个参数开始检查,第二个参数即参数列表。

    在主函数里调用调用 self_printf() 时,如果传入的参数不符合 printf() 标准函数的格式检查,那么就会发出警告或报错。比如将 self_printf("%d %d %s ", a, b, buf); 改为 self_printf("%d %d ", a, b, buf); 编译时会提示:
    引用
    beyes@linux-beyes:~/C/GNU_C_EXT> gcc -Wall -g attribute.c -o attribute.exe
    attribute.c: In function ‘main’:
    attribute.c:14: warning: too many arguments for format


    其实,在这里使用了 __attribute__ 属性后,事先就会对调用 seft_printf() 函数做检查。若检查通过,在其后的 self_printf() 内部的 switch() 里再检查格式字符串时,也没有必要再检查 % 了,也就是说,__attribute__ 保证了传入的参数是一定正确的。

    另外,__atribute__ format 中的 format 格式字符串,还可以按 scanf, strftime或strfmon 这些函数中的格式字符串规则进行检查。

    关于 va_start() , va_arg() , va_end() 的用法见: http://www.groad.net/bbs/read.php?tid=947

    2、__attribute_((noreturn)) 属性
    noreturn 属性用于 noreturn 函数,它告诉编译器被这个标识了这个属性的函数永不会返回。这可以让编译器生成稍微优化的代码,最重要的是可以消除不必要的警告信息,比如未初始化的变量。C 库函数中的 abort() 和 exit() 的声明格式就采用了这种格式,如:
    引用
    extern void exit(int) __attribute__((noreturn));
    extern void abort(void) __attribute__((noreturn));


    测试代码
    引用
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>

    /*__attribute__((noreturn))*/ void exitnow ()
    {
            exit(1);
    }

    int foo (int n)
    {

            if (n > 0) {
                    exitnow();
                    printf("hello world ");
            } else  return 0;
    }


    int main ()
    {
            int n = 10;
            foo (n);

            return 0;
    }

    编译一下
    引用
    beyes@linux-beyes:~/C/GNU_C_EXT> gcc -g -Wall attr_noreturn.c -o attr_noreturn.exe
    attr_noreturn.c: In function ‘foo’:
    attr_noreturn.c:17: warning: control reaches end of non-void function

    编译器发出警告,提示已经到达 ‘非void’ 函数的末尾。这里的 '非 void' 函数是 int foo(int n)。因为 foo() 函数应该有一个返回值,但是当 n > 0 时,编译器却无法找到返回值。如果在 printf() 函数后面添加一行,比如 return (1); 那么编译时警告消失。尽管 exitnow() 调用的是 exit() 函数且实际上程序在 n > 0 时也不会到达 printf() 函数,但编译器不会去检查 exitnow() 函数是什么,它仍然认为 exitnow() 后,程序会继续往下走。然而,我们可以在 exitnow() 的前面添加 __attribute__((noreturn))后(上面程序中去掉 foo() 前面的屏蔽部分),那么在不用添加 return (1); 语句的情况下,编译器也不会发出警告,因为 noreturn 属性明确告诉编译器:“ 到我这里,我不会返回了,你无需再发出警告”。


    3、__attribute__ const 属性

    该属性只能用于带有数值类型参数的函数上。当重复调用带有数值参数的函数时,由于返回值是相同的,所以此时编译器可以进行优化处理,除了第一次需要运算外,其它只需要返回第一次的结果即可,从而提高了效率。该属性主要适用于没有静态状态 (static state) 和副作用的一些函数,并且返回值仅仅依赖输入的参数。

    测试代码
    引用
    #include <stdio.h>

    __attribute__((const)) int square(int n)
    {
        return (n * n);
    }

    int main()
    {
        int i;
        int total = 0;
        for (i = 0; i < 100; i++)
            total += square(5) + i;

        printf ("total = %d ", total);
        
        return 0;
    }

    运行及输出
    引用
    beyes@linux-beyes:~/C/GNU_C_EXT> ./attr_const.exe 
    total = 7450

    说明
    如果 square() 函数前不加 const 属性,那么在主函数的 for 循环里,计算机会老老实实的进行 100 次的函数调用。而在加了 const 属性后,square() 函数只调用一次,其余的 99 次都一律直接返回第一次的值 : 25 ,而无需经过再次计算,这是因为 square(5) 的值是固定的。
    注意,带有该属性的函数不能有任何副作用或者是静态的状态。所以,像类似 getchar() 或 time() 这样充满“变数”的函数是不适合用该属性的。但是如果加了会怎么样呢?答案是没起作用。看下面代码:
    引用
    #include <stdio.h>
    #include <time.h>

    __attribute__((const)) time_t time_test(time_t *t)
    {
        return (time (t));
    }
    int main()
    {
        int i;
        time_t t = 0;
        for (i = 0; i < 5; i++) {
            printf ("%d ", (time_test(&t) + i));
            sleep(1);
        } 
        return 0;
    }

    两次运行输出
    引用
    beyes@linux-beyes:~/C/GNU_C_EXT> ./attr_const.exe 
    1250417600
    1250417602
    1250417604
    1250417606
    1250417608
    beyes@linux-beyes:~/C/GNU_C_EXT> ./attr_const.exe 
    1250417612
    1250417614
    1250417616
    1250417618
    1250417620

    由此可见,在 time() 这样的函数上即使加了 const 标签,那也不会看到一直返回一个固定值的情况;如果返回固定时间值,那上面的结果就会有奇数出现。

    4、__attribute__ ((packed));
    __attribute__((packed)) 属性用于变量和类型,用于变量或结构域时,表示使用最小可能的对齐,用于枚举、结构或联合类型时表示该类型使用最小的内存。如对于结构体,就是它告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。( 关于结构优化对齐<字节填充>,见:http://www.groad.net/bbs/read.php?tid=1037 )

    测试代码
    引用
    #include <stdio.h>

    struct demo {
            char i;
            char j;
            int k;
            int l;
            double m;
    }__attribute__((packed));

    typedef struct demo1 {
            char i;
            char j;
            int k;
            int l;
            double m;
    }__attribute__((packed)) test;

    typedef struct demo2 {
            char i;
            char j;
            int k;
            int l;
            double m;
    } demo_nopacked;


    typedef struct demo3 {
            char i;
            char j;
            int k;
            int l;
            double m;
    } demo_temp __attribute((packed));

    int main()
    {
            printf("sizeof demo is : %d ", sizeof(struct demo));

            printf("sizeof demo is : %d ", sizeof(test));

            printf("sizeof demo is : %d ", sizeof(demo_nopacked));

            printf("sizeof demo is : %d ", sizeof(demo_temp));

            return 0;
    }

    编译、运行及输出
    引用
    beyes@linux-beyes:~/C/base> gcc -g attr_pack.c -o attr_pack.exe
    attr_pack.c:34: warning: ‘packed’ attribute ignored

    beyes@linux-beyes:~/C/base> ./attr_pack.exe 
    sizeof demo is : 18
    sizeof demo is : 18
    sizeof demo is : 20
    sizeof demo is : 20

    如编译所提示的,__attribute__((packed)) 放在结构名 demo_temp 后面是要被忽略的。使用了 __attribute__((packed)) 标识的结构体,输出大小为自身实际字节占据的大小;没有标识的,则输出经过字节填充优化后的大小。

     

    beyes 2009-08-21 10:30
    __attribute__ 中的 section 属性对代码段起作用,其格式为:

    引用
    __attribute__ ((section("section_name")))

    其意是将作用的函数或数据放入指定名为 "section_name" 输入段中。

    输入段和输出段是相对于要生成最终的 elf 或 binary 时的 link 过程来说的。link 过程的输入大都是由源代码编译生成的目标文件.o ,那么这些 .o 文件中包含的段相对 link 过程来说就是输入段,而 link 的输出一般是可执行文件 elf 或库等,这些输出文件中也包含段,这些输出文件中的段叫做输出段。输入段和输出段没有必然联系,为互相独立,只是在 link 过程中,link 程序会根据一定的规则 (这些规则来源于 link script),将不同的输入段组合到不同的输出段中。

    测试代码-1
    引用
    #include <stdio.h>
    int main()
    {

        int var __attribute__ ((section(".xxdata"))) = 9;
        printf ("%d ", var);
        return 0;
    }

    编译
    引用
    beyes@linux-beyes:~/C/ELF> gcc -c test.c -o test.o
    test.c: In function ‘main’:
    test.c:7: error: section attribute cannot be specified for local variables

    原来 section 属性不能用来声明局部变量。下面把 var 改为全局变量:
    引用
    #include <stdio.h>
    int var __attribute__ ((section(".xxdata"))) = 9;
    int main()
    {
        printf ("%d ", var);
        return 0;
    }

    编译通过。下面查看一下 test.o 文件中的 section 信息:
    引用
    beyes@linux-beyes:~/C/ELF> objdump -x test.o

    test.o:     file format elf32-i386
    test.o
    architecture: i386, flags 0x00000011:
    HAS_RELOC, HAS_SYMS
    start address 0x00000000

    Sections:
    Idx Name          Size      VMA       LMA       File off  Algn
      0 .text         00000034  00000000  00000000  00000034  2**2
                      CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
      1 .data         00000000  00000000  00000000  00000068  2**2
                      CONTENTS, ALLOC, LOAD, DATA
      2 .bss          00000000  00000000  00000000  00000068  2**2
                      ALLOC
      3 .xxdata       00000004  00000000  00000000  00000068  2**2
                      CONTENTS, ALLOC, LOAD, DATA
      4 .rodata       00000004  00000000  00000000  0000006c  2**0
                      CONTENTS, ALLOC, LOAD, READONLY, DATA
      5 .comment      0000003a  00000000  00000000  00000070  2**0
                      CONTENTS, READONLY
      6 .comment.SUSE.OPTs 00000005  00000000  00000000  000000aa  2**0
                      CONTENTS, READONLY
      7 .note.GNU-stack 00000000  00000000  00000000  000000af  2**0
                      CONTENTS, READONLY
    SYMBOL TABLE:
    00000000 l    df *ABS*    00000000 test.c
    00000000 l    d  .text    00000000 .text
    00000000 l    d  .data    00000000 .data
    00000000 l    d  .bss    00000000 .bss
    00000000 l    d  .xxdata    00000000 .xxdata
    00000000 l    d  .rodata    00000000 .rodata
    00000000 l    d  .comment.SUSE.OPTs    00000000 .comment.SUSE.OPTs
    00000000 l    d  .note.GNU-stack    00000000 .note.GNU-stack
    00000000 l    d  .comment    00000000 .comment
    00000000 g     O .xxdata    00000004 var
    00000000 g     F .text    00000034 main
    00000000         *UND*    00000000 printf


    RELOCATION RECORDS FOR [.text]:
    OFFSET   TYPE              VALUE 
    00000012 R_386_32          var
    0000001d R_386_32          .rodata
    00000022 R_386_PC32        printf

    上面,.xxdata 是自定义 section。像在 linux 驱动程序设计中,模块加载函数前有一个 __init 宏,也用了 attribute 的 section 属性,如:
    引用
    #define __init __attribute__ ((__section__(".init.text")))

    说明:在 linux 内核中,所有标识为 __init 的函数在链接的时候都放在 .init.text 这个区段内。此外,所有的 __init 函数在区段 .initcall.init 中还保存了一份函数指针,在初始化时内核会通过这些函数指针调用这些 __init 函数,并在初始化完成后释放 init 区段 (包括 .init.text, .initcall.iinit 等)。

    不但是变量,函数也可以用 section 属性来声明
    引用
    #include <stdio.h>

    int var __attribute__ ((section(".xdata.text"))) = 9;
    int __attribute__ ((section(".xxdata"))) func (int var)
    {
        printf ("%d ", var);
        return 0;
    }
    int main()
    {
        func (var);
        
        return 0;
    }

    编译后,同样用 objdump 查看一下 section 信息
    引用
    beyes@linux-beyes:~/C/ELF> objdump -x test.o

    test.o:     file format elf32-i386
    test.o
    architecture: i386, flags 0x00000011:
    HAS_RELOC, HAS_SYMS
    start address 0x00000000

    Sections:
    Idx Name          Size      VMA       LMA       File off  Algn
      0 .text         0000002c  00000000  00000000  00000034  2**2
                      CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
      1 .data         00000000  00000000  00000000  00000060  2**2
                      CONTENTS, ALLOC, LOAD, DATA
      2 .bss          00000000  00000000  00000000  00000060  2**2
                      ALLOC
      3 .xdata.text   00000004  00000000  00000000  00000060  2**2
                      CONTENTS, ALLOC, LOAD, DATA
      4 .rodata       00000004  00000000  00000000  00000064  2**0
                      CONTENTS, ALLOC, LOAD, READONLY, DATA
      5 .xxdata       00000020  00000000  00000000  00000068  2**0
                      CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
      6 .comment      0000003a  00000000  00000000  00000088  2**0
                      CONTENTS, READONLY
      7 .comment.SUSE.OPTs 00000005  00000000  00000000  000000c2  2**0
                      CONTENTS, READONLY
      8 .note.GNU-stack 00000000  00000000  00000000  000000c7  2**0
                      CONTENTS, READONLY
    SYMBOL TABLE:
    00000000 l    df *ABS*    00000000 test.c
    00000000 l    d  .text    00000000 .text
    00000000 l    d  .data    00000000 .data
    00000000 l    d  .bss    00000000 .bss
    00000000 l    d  .xdata.text    00000000 .xdata.text
    00000000 l    d  .rodata    00000000 .rodata
    00000000 l    d  .xxdata    00000000 .xxdata
    00000000 l    d  .comment.SUSE.OPTs    00000000 .comment.SUSE.OPTs
    00000000 l    d  .note.GNU-stack    00000000 .note.GNU-stack
    00000000 l    d  .comment    00000000 .comment
    00000000 g     O .xdata.text    00000004 var
    00000000 g     F .xxdata    00000020 func
    00000000         *UND*    00000000 printf
    00000000 g     F .text    0000002c main


    RELOCATION RECORDS FOR [.text]:
    OFFSET   TYPE              VALUE 
    00000012 R_386_32          var
    0000001a R_386_PC32        func


    RELOCATION RECORDS FOR [.xxdata]:
    OFFSET   TYPE              VALUE 
    00000010 R_386_32          .rodata
    00000015 R_386_PC32        printf


    在 linux 内核源代码中,与段相关的重要宏定义有:
    __init , __initdata, __exit, __exitdata 及类似的宏。

    在 include/init.h 中可以看到:
    引用
    #define __init  __attribute__ ((__section__ (".init.text")))  __cold

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

    #define __exitdata   __attribute__ (( __section__ (".exit.data")))

    #define __exit_call  __attribute_used__ __attribute__ (( __section__ (".exitcall.exit")))

    #define __init_refok  oninline __attribute__ ((__section__ (".text.init.refok")))

    #define __initdata_refok __attribute__ ((__section__ (".data.init.refok")))

    #define __exit_refok noinline __attribute__ ((__section__ (".exit.text.refok")))

    .........

    #ifdef MODULE

    #define __exit  __attribute__ (( __section__ (".exit.text"))) __cold

    #else

    #define __exit __attribute_used__ __attribute__ ((__section__ (".exit.text"))) __cold

    #endif

    __init 宏常用的地方是驱动模块初始化函数的定义处;
    __initdata 常用于数据定义,目的是将数据放入名叫 .init.data 的输入段。
    需要注意的是,上面的定义中,用 __section__ 代替了 section 。还有其他一些类似定义的宏,作用也类似。

    ........... ........... ........... ........... ........... ........... ........... ........... ........... ........... ........... ........... ........... ...........

    关于 initcall 的宏定义
    这条宏定义更为重要,它是一条可扩展的宏:
    引用
    #define __define_initcall(level,fn,id) static initcall_t __initcall_##fn##id __used __attribute__((__section__(".initcall" level ".init"))) = fn


    上面 initcall_t 的定义为:
    引用
    typedef int (*initcall_t)(void);


    __used 的定义在 include/linux/compiler-gcc4.h 中找到(根据编译器的不同,gcc4 中的 4 可能为 3)为:
    引用
    #define __used            __attribute__((__used__)


    initcall 宏定义带有 3 个参数:
    level, fn, id

    分析一下这个宏:
    由上面知道,initcall_t 是个用来函数指针定义类型,所以 __initcall_##fn##id 就是一个函数指针,fn 则是一个已经定义好了的函数。这里 ## 符号表示一个连接符的作用,它实际上负责一个新的函数名的定义。先不考虑 __used , __attribute__ 这些声明,假设fn 是一个定义好的函数 func() 的函数名 func,id 值为 9,level 值为 7,那么经过宏定义并展开后变成:
    static initcall_t __initcall_func9

    这时,再考虑 __used , __attribute__ 这些声明的意义:
    __attribute__((__section__(".initcall" level ".init"))) 表示,函数(以上面的 __initcall_func9 为例)被放在 .initcall7.init 这个 section 中;__used 表示使用  .initcall7.init 这个 section 中的空间。

    上面宏定义并不直接使用,同样在 init.h 文件中找到如下的宏定义:
    引用
    #define core_initcall(fn)               __define_initcall("1",fn,1)
    #define core_initcall_sync(fn)          __define_initcall("1s",fn,1s)
    #define postcore_initcall(fn)           __define_initcall("2",fn,2)
    #define postcore_initcall_sync(fn)      __define_initcall("2s",fn,2s)
    #define arch_initcall(fn)               __define_initcall("3",fn,3)
    #define arch_initcall_sync(fn)          __define_initcall("3s",fn,3s)
    #define subsys_initcall(fn)             __define_initcall("4",fn,4)
    #define subsys_initcall_sync(fn)        __define_initcall("4s",fn,4s)
    #define fs_initcall(fn)                 __define_initcall("5",fn,5)
    #define fs_initcall_sync(fn)            __define_initcall("5s",fn,5s)
    #define rootfs_initcall(fn)             __define_initcall("rootfs",fn,rootfs)
    #define device_initcall(fn)             __define_initcall("6",fn,6)
    #define device_initcall_sync(fn)        __define_initcall("6s",fn,6s)
    #define late_initcall(fn)               __define_initcall("7",fn,7)
    #define late_initcall_sync(fn)          __define_initcall("7s",fn,7s)

    这些宏定义是为了方便使用 __define_initcall 宏的,上面每条宏第一次使用时都会产生一个新的输入段。

    ... ... ... ... ...... .... ... ... ... ... .... ... ... 
    (转)
    __setup宏的来源及使用__setup这条宏在LinuxKernel中使用最多的地方就是定义处理Kernel启动参数的函数及数据结构,请看下面的宏定义:
    #define __setup_param(str, unique_id, fn,early)  
     static char __setup_str_##unique_id[] __initdata__aligned(1) = str;
     static struct obs_kernel_param__setup_##unique_id
      __used__section(.init.setup)  
      __attribute__((aligned((sizeof(long)))))
      = { __setup_str_##unique_id, fn, early }

    #define __setup(str,fn)    
     __setup_param(str, fn, fn, 0)
    使用Kernel中的例子分析一下这两条定义:
    __setup("root=",root_dev_setup);
    这条语句出现在init/do_mounts.c中,其作用是处理Kernel启动时的像root=/dev/mtdblock3之类的参数的。
    分解一下这条语句,首先变为:
    __setup_param("root=",root_dev_setup,root_dev_setup,0);
    继续分解,将得到下面这段代吗:
    static char __setup_str_root_dev_setup_id[] __initdata__aligned(1) = "root=";
    static struct obs_kernel_param __setup_root_dev_setup_id
      __used __section(.init.setup)
      __attribute__((aligned((sizeof(long)))))
      = { __setup_str_root_dev_setup_id,root_dev_setup, 0 };

    这段代码定义了两个变量:字符数组变量__setup_str_root_dev_setup_id,其初始化内容为"root=",由于该变量用__initdata修饰,它将被放入.init.data输入段;另一变量是结构变量__setup_root_dev_setup_id,其类型为structobs_kernel_param, 该变理被放入输入段.init.setup中。结构struct structobs_kernel_param也在该文件中定义如下:
    struct obs_kernel_param {
     const char *str;
     int (*setup_func)(char *);
     int early;
    };
    变量__setup_root_dev_setup_id的三个成员分别被初始化为:
    __setup_str_root_dev_setup_id -->前面定义的字符数组变量,初始内容为"root="。
    root_dev_setup --> 通过宏传过来的处理函数。
    0 -->常量0,该成员的作用以后分析。
    现在不难想像内核启动时怎么处理启动参数的了:通过__setup宏定义obs_kernel_param结构变量都被放入.init.setup段中,这样一来实际是使.init.setup段变成一张表,Kernel在处理每一个启动参数时,都会来查找这张表,与每一个数据项中的成员str进行比较,如果完全相同,就会调用该数据项的函数指针成员setup_func所指向的函数(该函数是在使用__setup宏定义该变量时传入的函数参数),并将启动参数如root=后面的内容传给该处理函数。

     

    beyes 2009-08-21 11:34
    unused 属性用于函数和变量,表示该函数或变量可能不使用。

    测试代码
    引用
    #include <stdio.h>

    int main()
    {
        int ai = 10;
        int bi = 11;

        printf("%d ", bi);
        
        return 0;
    }

    编译一下
    引用
    beyes@linux-beyes:~/C/GNU_C_EXT> gcc -g -Wall attr_unused.c -o attr_unused.exe
    attr_unused.c: In function ‘main’:
    attr_unused.c:6: warning: unused variable ‘ai’

    在上面的编译中,必须使用 -Wall 选项才能产生 ai 变量没有被使用的警告信息,否则不会产生警告。

    程序中添加 unused 属性后
    引用
    #include <stdio.h>

    int main()
    {
        int __attribute__((unused)) ai = 10;
        int bi = 11;

        printf("%d ", bi);
        
        return 0;
    }

    这样,编译时,无警告信息产生。

     

     

  • 相关阅读:
    PIC32MZ tutorial -- Core Timer
    PIC32MZ tutorial -- OC Interrupt
    PIC32MZ tutorial -- External Interrupt
    PIC32MZ tutorial -- Watchdog Timer
    PIC32MZ tutorial -- Output Compare
    PIC32MZ tutorial -- Input Capture
    PIC32MZ tutorial -- 32-bit Timer
    log | logstash
    Vxlan学习笔记——原理
    python——字符串格式化
  • 原文地址:https://www.cnblogs.com/kwseeker-bolgs/p/4395043.html
Copyright © 2011-2022 走看看