GNU C 扩展
来源:互联网 发布:重生之网络大亨txt 编辑:程序博客网 时间:2024/04/28 03:20
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 分别两次,当参数有副作用时,将产生不正确的结果,见: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\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:24GNU 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_DEBUGfmt, ##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);
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");
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:21GNU 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:03GNU CC 预定义了两个标志符保存当前函数的名字,__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\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:25GNU C 提供了大量的内建函数,其中很多是标准 C 库的内建版本,例如 memcpy(),它们与对应的 C 库函数功能相同。而其他内建的名字通常以 __builtin 开始。
- __builtin_return_address (LEVEL)
下面是测试代码:
引用#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)
测试代码:
引用#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)
测试程序:
引用#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");
return0;
}
b = 8;
if ( __builtin_expect((expect(a, b)),TST)) {
printf ("expected TST\n");
return0;
}
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在 http://www.groad.net/bbs/read.php?tid=1032 中转载了一篇关于 __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 <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)) 属性用于变量和类型,用于变量或结构域时,表示使用最小可能的对齐,用于枚举、结构或联合类型时表示该类型使用最小的内存。如对于结构体,就是它告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。( 关于结构优化对齐<字节填充>,见: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\n",sizeof(structdemo));
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 (intvar)
{
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:34unused 属性用于函数和变量,表示该函数或变量可能不使用。
测试代码:
引用#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;
}
这样,编译时,无警告信息产生。
0 0
- GNU C扩展
- GNU C扩展(一)
- GNU C扩展(一)
- 扩展GNU C
- GNU C 扩展
- GNU C的扩展
- GNU C扩展
- GNU C的扩展
- GNU C的扩展
- GNU C的扩展
- GNU C 扩展
- GNU C 扩展
- GNU C 扩展
- GNU C 扩展(二)
- GNU C 扩展(二)
- GNU对C的扩展
- GNU C 对标C的扩展
- Linux 内核使用的 GNU C 扩展
- 选择排序之直接选择排序
- http://blog.yufeng.info/archives/2456
- yii phpexcel自动生成文件保存到服务器上
- nyoj 48 小明的调查作业(排序之去掉相同元素)
- 应用获取ROOT权限实践
- GNU C 扩展
- ThinkPHP 导航栏鼠标按下动态更新
- 水处理过滤器:水处理过滤器的维护及保养方法
- Android Volley完全解析
- 理解与使用javascript中的回调函数
- 读《大数据》的三重大思维转变,有感
- Ubuntu 环境变量
- 伟大的程序员是怎样炼成的?
- Win7+VS2010+QGIS2.0以上版本编译流程