共计 4444 个字符,预计需要花费 12 分钟才能阅读完成。
今天在 linux 下使用了 system 这个库函数,system 函数的原型是
int system(const char* string);
system 函数的执行时调用 /bin/sh -c string 来执行指定参数的字符串,返回参数是子进程的退出状态,下面是我的测试小列子:
/* | |
* test2.c | |
*/ | |
int main(int argc, const char *argv[]) | |
{printf("hello world!\n"); | |
return 1; | |
} |
/* | |
* test.c | |
*/ | |
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)的定义
((((__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__你现在可以理解成一个空格,后面会解释
/* | |
* test3.c | |
*/ | |
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;...}),复合语句的值是最后一个表达式的值
/* | |
* test4.c | |
*/ | |
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 里面关于这个的一个应用, 正常情况下我们定义一个求最小值的宏, 是这样定义的
#define min(x, y) ((x) < (y) ? (x) : (y))
当我们以这样的方式调用这个宏,和我们预想的结果不一样
/* | |
* test5.c | |
*/ | |
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; | |
} |
现在我们以复合语句的方式来定义
typeof(x) __x = (x);\ | |
typeof(y) __y = (y);\ | |
(void)(&__x == &__y);\ | |
(__x < __y) ? __x : __y;\ | |
}) |
下面是测试小列子:
/* | |
* test6.c | |
*/ | |
(__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} 这个的形式来得到一个临时变量, 这种形式类似于强制类型转换
/* | |
* test7.c | |
*/ | |
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__关键字
__attribute__关键字用于对变量,函数声明等进行一下约束信息
__attribute__((always_inline)) // 声明此函数式内联函数
__attribute__((constructor)) // 强调此函数在 main 函数之前被调用
__attribute__((destructor)) // 强调此函数在 main 函数结束之后被调用
/* | |
* test8.c | |
*/ | |
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"); | |
} |
打印出
before enter main.
main.
exit main.
这里的__attribute__((destructor))用法和 atexit()函数的调用很相像,不过一个是根据声明的顺序调用,一个根据入栈规则调用
更多关于__attribute__的用法请参考http://gcc.gnu.org/onlinedocs/gcc-4.1.2/gcc/Function-Attributes.html#Function-Attributes
七. 关于几个内建函数的用法
下面是关于内建函数的声明
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 的返回值
/* | |
* test9.c | |
*/ | |
void* __builtin_apply_args(); | |
void* __builtin_apply(void (*function)(), void* arguments, size_t size); | |
void __builtin_return(void* result); | |
void log(const char* string, ...) | |
{void* arg = __builtin_apply_args(); | |
printf("print log\n"); | |
__builtin_apply(printf, arg, 128); | |
} | |
int main(int argc, const char *argv[]) | |
{log("file: %s, line %d.\n", __FILE__, __LINE__); | |
return 0; | |
} |
如果没有这种技术,而要在 log 函数中调用 printf 函数,就非常困难了,除非使用 va_start, va_end 这些宏构造参数,然后调用 vfprintf,然而有了这三个函数,就非常简单。
现在在让我们回头来看 WEXITSTATUS(ret)的定义
((((__extension__ (((union { __typeof(ret) __in; int __i; }) {.__in = (ret) }).__i))) & 0xff00) >> 8);
这个宏的先用 ret 的值定义一个联合体的临时变量,在取临时变量的值的 8 -16 位,得到子进程的退出状态

很不错的一篇总结:
1、对system的返回值做了深入的研究,由WEXITSTATUS宏来获得;
2、成员初始化,typeof关键字,复合语句,__attribute__关键字及宏的扩展都做了详细的说明与举例。
3、main函数一定是第一个执行的吗?不一定,看了这篇文章后你就知道。
4、GNU的几个内建函数的用法。