uncategorized

GNU C对ISO标准的扩展

这个大学时候写的, 由于blog迁移, 放到这里.

在linux下使用system这个库函数, system的函数的原型是:

1
int system(const char* string);

system函数的执行时调用/bin/sh -c string来执行指定参数的字符串,返回参数是子进程
的退出状态,下面是我的测试小列子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/*
* test2.c
*/


#include <stdio.h>

int main(int argc, const char *argv[])
{

printf("hello world!\n");
return 1;
}

/*
* test.c
*/


#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>

int main(int argc, const char *argv[])
{

int ret = system("./test2");

printf("ret = %d\n", ret); //ret = 256
printf("WEXITSTATUS(ret) = %d\n", WEXITSTATUS(ret)); //WEXITSTATUS(ret) = 1
return 0;
}

分别编译上面的两个文件gcc test2.c -o test2 , gcc test.c -o test, 然后执行./test
程序,你会发现ret的值和宏得到的值有点不同,宏所得到的值才是我们想要的子进程的退出码,
查了点资料发现,子进程的退出码是放在整形数的8-16位的,所以有了上面的值的不同,同时我
WEXITSTATUS的定义有了兴趣,使用gcc -E test.c -o test.i得到预处理文件,下面是
WEXITSTATUS(ret)的定义

1
((((__extension__ (((union { __typeof(ret) __in; int __i; }) { .__in = (ret) }).__i))) & 0xff00) >> 8);

这个定义使用了gun/c的扩展语法, 下面我先来介绍一下gun/c的常用扩展语法,再来解释上面的宏

一.Designated Initializers(指定成员初始化)

int array[6] = {[4] = 29, 2 = 15}; 和这个是等价的int array[6] = {0, 0, 15, 0, 29, 0}

struct point {int x; int y};

union foo {int i; double d};

当初始化一个结构体成员时,我们可以这个来初始化

struct point p = {.y = 2, .x = 1};他是和struct point p = {1, 2}是等价的

union foo f = {.d = 4};

二.typeof关键字(typeof, typeof__)

typeof(expression)用于得到一个表达式的类型, 下面的extension你现在可以理解成一个空格,后面会解释

1
2
3
4
5
6
7
8
9
10
11
12
13
/*
* test3.c
*/


#include <stdio.h>

int main(int argc, const char *argv[])
{

__extension__ typeof(1) i = 100; //typeof(1)取得1的类型是int,然后用int定义一个变量

printf("i = %d\n", i); //i = 100
return 0;
}
三.Statements and Declarations in Expressions(复合语句声明)

({expression;…}),复合语句的值是最后一个表达式的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
* test4.c
*/

#include <stdio.h>

int main(int argc, const char *argv[])
{
int result = __extension__ ({
int i = 1, sum = 0;

for(; i<=100; i++)
sum += i;
sum; //复合语句块的值就是sum的值
});

printf("result = %d\n", result); //reslut = 5050
return 0;
}

说了复合语句块,我们来gnu里面关于这个的一个应用,正常情况下我们定义一个求最小值的宏,是这样定义的

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

当我们以这样的方式调用这个宏,和我们预想的结果不一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/*
* test5.c
*/


#include <stdio.h>

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

int main(int argc, const char *argv[])
{

int x = 1, y = 2;

int ret = min(x++, y++); //我们期望的结果是1

printf("ret = %d\n", ret); //ret = 2,这里就有宏的副作用了,进行简单的文本替换
return 0;
}

现在我们以复合语句的方式来定义

1
2
3
4
5
6
#define min(x, y) __extension__ ({\
typeof(x) __x = (x);\
typeof(y) __y = (y);\
(void)(&__x == &__y);\
(__x < __y) ? __x : __y;\
})

下面是测试小列子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
* test6.c
*/


#include <stdio.h>

#define min(x, y) __extension__ ({\
typeof(x) __x = (x);\
typeof(y) __y = (y);\
(void)(&__x == &__y);\

(__x < __y) ? __x : __y;\
})

int main(int argc, const char *argv[])
{

int x = 1, y = 2;

int ret = min(x++, y++);

printf("ret = %d\n", ret); //ret = 1,这个就避免了宏的副作用
return 0;
}

上面的(void)(&x == &y)是判断x和y是不是同一种类型,如果编译器会警告.

Compound Literals

gun允许我们以这样的(type){expresion}这个的形式来得到一个临时变量, 这种形式类似于强制类型转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*
* test7.c
*/


#include <stdio.h>

typedef struct student
{
int id;
char name[10];
}Stu;

int main(int argc, const char *argv[])
{

Stu s = __extension__ (Stu){.id = 1, .name = "zhang"}; //这里和类型转换很相像,等价于Stu s = {1, "zhang"};

printf("id = %d, name = %s\n", s.id, s.name); //id = 1, name = zhang
return 0;
}
五.extension关键字

上面的列子中使用了gnu对c语言的扩展,我们都加上了extension关键字,如果不加extension 关键字,当我们编译的是加上-pedantic选项时,会产生警告信息,extension关键字
就是说使用gun对c语言的扩展,编译时加上-pedantic选项不要产生警告信息

六.attribute关键字
1
2
3
4
5
6
7
__attribute__  //关键字用于对变量,函数声明等进行一下约束信息  

__attribute__((always_inline)) //声明此函数式内联函数

__attribute__((constructor)) //强调此函数在main函数之前被调用

__attribute__((destructor)) //强调此函数在main函数结束之后被调用

下面举个使用__attribute__的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
* test8.c
*/


#include <stdio.h>

void enter_main(void) __attribute__ ((constructor));
void exit_main(void) __attribute__ ((destructor));

int main(int argc, const char *argv[])
{

printf("main.\n");

return 0;
}

void enter_main(void)
{

printf("before enter main.\n");
}

void exit_main(void)
{

printf("exit main.\n");
}

上面的结果是:

1
2
3
4
5
before enter main.

main.

exit main.

这里的attribute((destructor))用法和atexit()函数的调用很相像,不过一个是根据
声明的顺序调用,一个根据入栈规则调用, 更多关于__attribute__的用法请参考这
Function-Attributes.

七.关于几个内建函数的用法

下面是关于内建函数的声明

1
2
3
4
5
void* __builtin_apply_args();

void* __builtin_apply(void (*function)(), void* arguments, size_t size);

void __builtin_return(void* result);

下面是一个小列子,关于这几个内建函数的使用,假如函数f中需要调用另一个函数g,并且传
给g的参数和传给f的参数完全一样,则在f中可以使用第一个函数__builtin_apply_args
造参数,使用__builtin_apply完成g的调用,使用__builtin_return保存g的返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/*
* test9.c
*/


#include <stdio.h>

void* __builtin_apply_args();

void* __builtin_apply(void (*function)(), void* arguments, size_t size);

void __builtin_return(void* result);

// log函数相当于f函数
void log(const char* string, ...)
{

void* arg = __builtin_apply_args();

printf("print log\n");
// printf函数相当于g函数
__builtin_apply(printf, arg, 128);
}

int main(int argc, const char *argv[])
{

log("file: %s, line %d.\n", __FILE__, __LINE__);
return 0;
}

如果没有这种技术,而要在error函数中调用printf函数,就非常困难了,除非使用
va_start, va_end这些宏构造参数,然后调用vfprintf,然而有了这三个函数,就非常简单。

现在在让我们回头来看WEXITSTATUS(ret)的定义

1
((((__extension__ (((union { __typeof(ret) __in; int __i; }) { .__in = (ret) }).__i))) & 0xff00) >> 8);

这个宏的先用ret的值定义一个联合体的临时变量,在取临时变量的值的8-16位,得到子进程的退出状态.

扩展阅读

参考