Chinaunix首页 | 论坛 | 博客
  • 博客访问: 967621
  • 博文数量: 403
  • 博客积分: 27
  • 博客等级: 民兵
  • 技术积分: 165
  • 用 户 组: 普通用户
  • 注册时间: 2011-12-25 22:20
文章分类

全部博文(403)

文章存档

2016年(3)

2015年(16)

2014年(163)

2013年(222)

分类: LINUX

2013-05-13 14:15:03

原文地址:GNU C 扩展 作者:wjjl

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

而标准的宏定义为:
引用
#define min(x,y) ((x) < (y) ? (x) : (y))

这个定义计算 x 和 y 分别两次,当参数有副作用时,将产生不正确的结果,见:


修改上面链接代码如下
引用
#include

#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\n", j);
   
    printf("%d\n", *p);

    return (0);
}

运行及输出
引用
beyes@linux-beyes:~/C/micro> ./micro2.exe
8
9

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

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

测试代码
引用
#include

#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\n", j);

        printf("%f\n", *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
#include
#include

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\n");
                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\n", 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

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

int main()
{
    char a[20] = "hello world\n";
    int i = 10;
    debug("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

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

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

    return 0;
}

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

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

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

int main()
{

    debug("hello world\n");

    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\n" 代入 format,然而,在其后还紧跟着一个逗号,但是这个逗号后面是期望有 args 参数的,但这里却没有,所以宏不能展开完全,故而无法编译通过。那么,再改一下宏定义:
引用
#include

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

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

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

#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("\n");

    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

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

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 CC 预定义了两个标志符保存当前函数的名字,__FUNCTION__ 保存函数在源码中的名字,__PRETTY_FUNCTION__ 保存带语言特色的名字。在 C 函数中,这两个名字是相同的,在 C++ 函数中,__PRETTY_FUNCTION__ 包括函数返回类型等额外信息, 内核只使用了 __FUNCTION__。



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

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


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


测试代码
引用
#include

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

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

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

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

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

int main()
{

    builtin_func();

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

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


看一下 builtin.exe 的反汇编代码
引用
08048436
:
 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
 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
 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

#define SIZE 100

int main()
{   
    int k;
    k = __builtin_constant_p (SIZE);   
   
    if (k == 1) {
        printf("SIZE is constant\n");
        return 0;
    } else {
        printf("SIZE is not constant\n");
        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

#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\n");
                return 0;
        }

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

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

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

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

beyes 2009-08-16 12:38
在 中转载了一篇关于 __attribute__ 机制说明的文章,这里再用完整的一般测试代码进行测试,作为加深理解与补充。

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
#include

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\n", 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 '\n':
                            c = (char)va_arg (ap, int);
                            printf("\n");
                            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\n", a, b, buf); 改为 self_printf("%d %d\n", 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, strftimestrfmon 这些函数中的格式字符串规则进行检查。

关于 va_start() , va_arg() , va_end() 的用法见:

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


测试代码
引用
#include
#include
#include

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

int foo (int n)
{

        if (n > 0) {
                exitnow();
                printf("hello world\n");
        } 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

__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\n", 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
#include

__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\n", (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)) 属性用于变量和类型,用于变量或结构域时,表示使用最小可能的对齐,用于枚举、结构或联合类型时表示该类型使用最小的内存。如对于结构体,就是它告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。( 关于结构优化对齐<字节填充>,见: )

测试代码
引用
#include

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\n", sizeof(struct demo));

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

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

        printf("sizeof demo is : %d\n", 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
int main()
{

    int var __attribute__ ((section(".xxdata"))) = 9;
    printf ("%d\n", 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
int var __attribute__ ((section(".xxdata"))) = 9;
int main()
{
    printf ("%d\n", 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

int var __attribute__ ((section(".xdata.text"))) = 9;
int __attribute__ ((section(".xxdata"))) func (int var)
{
    printf ("%d\n", 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

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

    printf("%d\n", 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

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

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

这样,编译时,无警告信息产生。
阅读(435) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~