三国杀是个很有意思的游戏,在游戏的过程中有一些封号有意思,“初尝胜果”,“旗开得胜”....都是刚开始的胜利,可圈可点;然而高手与熟手的区别在于能否对自己用的工具领悟得更多。
在这个技术知识爆炸的年代,很多人都是掌握一门基本技艺,然后重复之,如此了此一生;而学习一门技艺,却需要不断地去淬炼它,用力夯实基础,以此为基,跟进一步,集之大成。
在熟练使用gcc作为编译工具的基础上,还需要理解gnc的c语言的扩展;标准的c语言更加通用,能够在各种平台上使用,而c语言的方言,更加灵活,方便。在此基础上,随着linux平台的广泛使用,gnu的c语言扩展成为一项很有用的技艺,值得让我们去学习。
gnu c是在标准c语言的基础上,做了很多扩展,可以看作一种C语言的方言。如下是在gcc使用手册(https://gcc.gnu.org/onlinedocs/gcc-4.9.2/gcc/index.html#toc_C-Extensions;第六节——Extensions to the C Language Family)上摘取的一些有利于c语言编程的扩展;同样这些技巧也是被反复使用在linux内核的源码中与其他开发源代码中;其实,这也是很好的一种总结的方法,将好的编程方法用程序库,应用工具,文档,甚至语言积累下来。
如下为一系列常用的实例与简单实用,更详细的,请参考官方文档;具体实例,请参考附件的c文件。
1.表达式的扩展以”({})”形式进行扩展,例子如下:
({ int y = foo (); int z;
if (y > 0) z = y;
else z = - y;
z; })//z可以看作表达式输出
使用场景:将复杂的表达式作为宏放到代码中使用,让代码更加简洁,易懂。
2.局部标记的支持,用__label__关键字声明一个标签,然后用goto跳转;标签在程序中的表现就是地址,这样处理标签,可以更加灵活的控制程序流程。
#define SEARCH(value, array, target) \
do { \//将复杂的逻辑定义为宏的一种技巧。
__label__ found; \//局部标记的定义
typeof (target) _SEARCH_target = (target); \//typeof标准字的使用,得到target的类型
typeof (*(array)) *_SEARCH_array = (array); \
int i, j; \
int value; \
for (i = 0; i < max; i++) \
for (j = 0; j < max; j++) \
if (_SEARCH_array[i][j] == _SEARCH_target) \
{ (value) = i; goto found; } \//局部标记的使用
(value) = -1; \
found:; \//局部标记的实现
} while (0)
3.将标签当作变量来使用,标签被以"&&"开头来引用,被以"void*"的指针来指向,被goto时需要用*来得到其值。
int label_val(int i,int type)
{
void*label_array[]={&&do_add,&&do_sub,&&do_multi,&&do_div};//定义标签数组
goto *label_array[type];//标签被引用
do_add:
return i++;
do_sub:
return i--;
do_multi:
return i*i;
do_div:
return 1/i;
}
4.函数内部定义函数,只能在被定义的函数内部访问,这样就控制了函数的访问权限,更有利于模块化。
int nested_func(int a,int b)
{
auto int add();//定义 内部add函数,需要auto声明
int do_add(int(*add)()){//在定义内部函数,调用内部函数,能够引用参数
return add();
}
int c = do_add(add);//内部函数被调用
if(c>0)
return c;
else
return 0;
int add(){ return a+b;}//内部函数实现
}
5.内置函数,增强c语言对程序的控制,分为几类:
a)获取函数调用关系的函数,有利于调试函数的调用堆栈:__builtin_return_address(int level).得到调用者的地址。
b)获取调用函数传递的参数,间接调用函数以及得到函数的返回值:void * __builtin_apply_args ()得到参数列表。
6.关键字typeof的使用,获取变量的类型定义,能够动态获取数据类型,更加灵活,准确的描述数据类型。
#define max(a,b) \
({ typeof (a) _a = (a); \
typeof (b) _b = (b); \
_a > _b ? _a : _b; })
7.扩展标准c表达式的灵活处理,让程序更加容易读懂:
a)结构体与数组的初始化
b)case 语句的连续表示
c)"?:"运算符的简化
e)长度为0的数组:定义时,节约空间
f)长度可变的数组,动态数组的支持
g)可变参数的宏定义
h)数组非常量初始化
i)灵活的动态数据转换
j)混合声明与定义
k)offsetof(type,member)的使用:得到member在结构体type中的偏移(stddef.h)中定义,一个重要的实例——container_of(通过局部指针得到整体指针),在linux内核中,链表的使用
#define container_of(ptr, type, member) ({\
const typeof( ((type *)0)->member ) *__mptr = (ptr);\
(type *)( (char *)__mptr - offsetof(type,member) );})
//offsetof的扩展,通过结构体(type)的域(member)指针(ptr)得到指向该结构体的指针。
#define debug(format, ...) fprintf (stderr, format,## __VA_ARGS__)
//定义可变参数的宏,"..."对应"##__VA_ARGS__"支持只有format参数。
#define warning(format0,args...) fprintf(stdout,format0, args)
void basic_extend()
{
int array[5] = {[2]=1,[3 ... 4]=3};//数组的初始化,第2个参数为1,第3到4个参数为3(注意“...”旁边需要有空格)
struct point { int x, y; };
struct point p0 = {//结构体的初始化
.x = 5,
.y =6
};
int i =0;
int v = 8 ;
for(i=0;i<5;i++){
switch(i){
case 1 ... 3 ://case语句的连续表达
v = array[i];
break;
case 4:
v = array[i]?:p0.y;//"?:"的简化表达
break;
}
}
struct line {
int len;
char p[0];//长度为0的数组声明
};
struct line *one;//混合声明与定义的表达
int len = 5;
one = (struct line *)malloc(sizeof(struct line)+len+1);
one->len = len;
strcpy(one->p,"this");
printf("one.p=%s\n",one->p);
free(one);
struct S { int x[v]; };//数组的长度动态设置
struct S Sex ;
printf("size Sex:%d\n",sizeof(Sex));
for(len =0;len
Sex.x[len] = len;
printf("%d\n",Sex.x[len]);
}
int av[4] = {v ,len,Sex.x[2] ,i};//数组的动态初始化
printf("av[2] = %d\n",av[2]);
struct Fst {int a;char b[2]} fst;
fst = ( struct Fst ){v,len,i};
printf("fst.b[1]=%d\n",fst.b[1]);
static short sb[] = (short[3]){'6',8,9};
char *fst_byts = (char*)&fst;
printf("sb[0]=%d\n",sb[0]);
printf("offsetof(struct Fst,a) = %d\n",offsetof(struct Fst,a)); //offset的实例
printf("v:%d\n",fst.a);
char *pb = fst.b;
struct Fst *pst = container_of(pb,struct Fst,b);//container_of的实例
printf("pst'a :%d\n",pst->a);
}
8.gnu 属性(__attribute__)的使用,增加c语言的表达能力,增强在编译与链接时对程序的控制,主要用于函数,变量与数据结构定义。具体的属性含义与使用,请参考gnu的官方网址:https://gcc.gnu.org/onlinedocs/gcc-4.9.2/gcc/Attribute-Syntax.html#Attribute-Syntax
a)编译对函数调用的优化,对程序进行更严格的检测,用于声明函数,不能用于函数定义->
https://gcc.gnu.org/onlinedocs/gcc-4.9.2/gcc/Function-Attributes.html#Function-Attributes
b)指定变量的相关属性,比如:对齐,链接到新的段等->
https://gcc.gnu.org/onlinedocs/gcc-4.9.2/gcc/Variable-Attributes.html#Variable-Attributes
c)指定数据结构定义的属性,比如:对齐,数据分布等->
https://gcc.gnu.org/onlinedocs/gcc-4.9.2/gcc/Type-Attributes.html#Type-Attributes
void attr_func() __attribute__((aligned(16)));//函数属性的定义
void attr_func()
{
struct Attr1{
int a;//4
short b;//2
char c;//2偶地址对齐
};
struct Attr_P{
int a;//4
short b;//2
char c;//1
}__attribute__((packed));//结构体属性的定义
int a __attribute__((aligned(32))) = 6;//变量属性的定义
printf("a addr:0x%x\n",&a);
printf("Attr1 size:%d\n",sizeof(struct Attr1));
printf("Attr_P size:%d\n",sizeof(struct Attr_P));
printf("%s addr:0x%x\n",__func__,attr_func);
}
9.volatile 属性的使用,告之编译器,该对象不能够被优化处理。
10.asm用于c语言内嵌汇编。主要有如下3方面的用途:
a)实现c语言无法实现的操作,或者为c语言提供底层的接口
b)控制程序运行状态,比如:读取与修改寄存器值
c)优化c语言程序,在程序关键路径上,用汇编代替c语言实现对应功能
实现形式:
asm(汇编模板:输出参数列表:输入参数列表:其他约束);
(1)汇编模板——GNU用的AT&T的汇编语法
学习与AT&T汇编的方法:
(i)熟悉Intel汇编语法,学习它是因为有很多实例与教程,通过NASM命令来汇编对应的汇编程序。
生成elf文件(默认生成bin文件):nasm -f elf asm.elf
(ii)AT&T汇编语法,了解AT&T语法与Intel语法的差异,可参考附件《gas_manual-unix汇编》
(iii)汇编转换用objdump来进行反汇编成对应的汇编语言,然后通过对比,来理解与加深对汇编的理解
AT&T汇编:objdump -j .text -d asm.elf -m i386
Intel汇编:objdump -j .text -d asm.elf -m i386:intel
汇编模板:主要是汇编指令——操作码,操作数(固定操作数,替换操作码——由输入/输出列表指定)
参数列表:主要是建立c语言变量与汇编操作数的对应规则——参数替换式与引用式;c语言一般的变量是保存在内存(局部变量是在堆栈中,全局变量是在数据段)中的(除非用register指定),所以一般要运算需要将参数放到寄存器中——变量要运算需要寄存器作为载体。参数列表格式:”约束”(c语言变量表达式)
多条指定的支持:多条指令,以“\n\r”或者“;”分割。
(2)输出列表
将c语言的变量作为参数放到汇编运算的写入的位置,将汇编操作数中的值写入到输出的列表
(3)输入列表
将c语言的变量作为参数放入到汇编模板中指定的位置
(4)相关约束(clobber list)
汇编指令列表中的相关操作数(寄存器列表或者“memory”),会被其他方法修改的约束,比如:硬件或者其他软件指令流会对该操作数进行影响。
11.使用代码覆盖测试工具——gcov,对程序代码进行分析,从而达到优化代码的目的,也可参考https://gcc.gnu.org/onlinedocs/gcc-4.9.2/gcc/Invoking-Gcov.html#Invoking-Gcov
以如下代码为例:
(1)编辑源文件(gcov-c.c),如下:
int main()
{
int i=0;
int sum =0;
for(;i<10;i++)
sum += i;
if(sum >= 50)
{
printf("sum = %d >= 50\n",sum);
}else{
printf("sum = %d < 50\n",sum);
}
return 0;
}
(2)gcc带测试参数编译:
gcc -fprofile-arcs -ftest-coverage gcov-c.c -o gcov.out
(3)执行生成的bin文件:./gcov.out
(4)用gcov命令分析源文件,生成分析之后的文件gcov-c.c.gcov:
gcov gcov-c.c
(5)查看分析gcov-c.c.gcov文件:
执行次数 源文件行号 源文件信息
-: 0:Source:gcov-c.c
-: 0:Graph:gcov-c.gcno
-: 0:Data:gcov-c.gcda
-: 0:Runs:1
-: 0:Programs:1
1: 1:int main()
-: 2:{
1: 3: int i=0;
1: 4: int sum =0;
11: 5: for(;i<10;i++)
10: 6: sum += i;
1: 7: if(sum >= 50)
-: 8: {
#####: 9: printf("sum = %d >= 50\n",sum);
-: 10: }else{
1: 11: printf("sum = %d < 50\n",sum);
-: 12: }
1: 13: return 0;
-: 14:}
以上文件可以看出,文件总共有14行,有8行被执行了。主要花费时间在5/6行中,其中第9行没有被执行。
如果需要优化,则需要对5/6行进行优化,而第7/9行也可以直接去掉。