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 分别两次,当参数有副作用时,将产生不正确的结果,见:修改上面链接代码如下

引用

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

printf("%f\n", *p);

return (0);

}

运行及输出

引用

beyes@linux-beyes:~/C/micro> ./micro3.exe

8.100000

9.100000

说明

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

beyes2009-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\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 ,所以它是一个零长度数组。但是,零长度数组的作用不是用来定义一个结束地址,而是为了将来的扩展。结构体本身类似于一个信息头。同时,此结构只能通过堆方式分配内存。注意的是,结构体中的数组不是非得是零长度数组才,如果定义了长度也是可以的,但是如果基于将来进行扩展的目的,这就显得没有必要了。

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

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

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\n");   //预编译结果: fprintf(stderr, "hello C\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 <stdio.h>

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

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

beyes2009-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("\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。

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

测试代码

引用

#include <stdio.h>

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

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

引用

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\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__ 的宏,之所以使用这个名字,是因为它已经得到了广泛的支持。

beyes2009-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\n", 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\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 <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\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,编译器可以根据这个信息适当地重排语句块的顺序,使程序在预期的情况下有更高的执行效率。

beyes2009-08-16 12:38

__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\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, 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\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 <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\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 <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\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 <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\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)) 标识的结构体,输出大小为自身实际字节占据的大小;没有标识的,则输出经过字节填充优化后的大小。


beyes2009-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\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 <stdio.h>

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 <stdio.h>

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=后面的内容传给该处理函数。

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

测试代码

引用

#include <stdio.h>

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 <stdio.h>

int main()

{

int __attribute__((unused)) ai = 10;

int bi = 11;

printf("%d\n", bi);

return 0;

}

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