设为首页 加入收藏

TOP

C编译器剖析_C语言的变参函数(二)
2015-03-19 03:34:57 来源: 作者: 【 】 浏览:302
Tags:编译器 剖析 语言 函数
)和(2+3)运算的目的是为了得到一个不小于4的数,而&(~3)运算的目的则是把这个不小于4的数的低2位清0,从而得到我们需要的对齐结果。如果宏定义va_arg中没有进行对齐操作,即把va_arg()定义为:

#define va_arg(list, t) (*(t *)((list+= sizeof(t)) - sizeof(t)))

不妨假设变量list的值为十进制的20000,则va_arg(list,char)宏展开后得到的是如下表达式,此表达式实际上相当于*((char*)20000),即取内存单元20000中的内容,但是该表达式是有副作用的,完成求值后,list变量的值变为20001。参数入栈时已经按sizeof(int)进行对齐,如果接下来按照一个不是4倍数的地址20001去取下一个参数,则我们无法正确的访问到所需的参数。

(*(char *)((list += 1) - 1))

宏定义va_arg是否有进行形如图4.2.15第9行那样的对齐,则与实际所用编译器的头文件有关。最稳妥最有可移植性的做法就是不要去使用va_arg(list,char)、va_arg(list,short)或者va_arg(list,float)。因为C编译器已经对变参函数的“无名参数”进行了实参提升,在栈中真正存在的“无名参数”不会是char,也不会是short,更不会是float,所以在图4.2.15第20行中我们用的是va_arg(ap,int),而非va_arg(ap,char)。当然,如果有经过第9行的ALIGN_INT的对齐,一定要用va_arg(ap,char)也不并非完全不可。但是,若使用va_arg(ap,float)则仍然会出问题,原因是sizeof(float)为4,而在栈在实际存在的double要占8个字节。C语言简洁而有力,但是要较好地驾驭C语言,则需要对形如图4.2.12的内存布局有较清楚的理解。在很多时侯,一些C程序员无法较好使用C语言指针的原因在于“对相关内存布局没有较清晰的概念”。C++实际上也对程序员提出了类似的要求,即便是在有意淡化指针概念的Java语言,如果对内存布局完全没有概念,也是有可能写出如下所示的Java代码。该程序员原本期待两次对bg.f()的调用能各打印出5条Hello,即共10条Hello,但却诧异地发现一共只有5条Hello。

class Bug{

int i= 0;

public void f(){

for(;i < 5; i++){

System.out.println(“Hello”);

}

}

public static void main(String args[]){

Bug bg = new Bug();

bg.f();

bg.f();

}

}

当然,实际使用va_arg等变参函数的宏时,我们只需要包含标准头文件stdarg.h就可以,并不需要显示地定义图4.2.15中第8至12行的宏。这些宏来自于UCC编译器的头文件ucl\linux\include\stdarg.h。有些时侯,我们在形如图4.2.15的变参函数OurPrintfV2中,只是想做一些准备工作,真正的对栈中的“无名参数”的访问的操作,我们还是想交由另外一个函数来处理,比如ucl\error.c中的Do_Error函数。如图4.2.16所示。

\

图4.2.16 Do_Error()

图4.2.16第7行,我们使用于记录错误个数的全局变量ErrorCount加1,第10行用于打印出出错的源代码的文件名和出错的行号,但是真正的错误提示信息的打印,我们还是想交给库函数vfprintf(),结合图4.2.13,我们很容易知道,vfprintf()函数只要知道格式化字符串的首地址,和Do_Error函数的形参format的地址,就可以从Do_Error对应的栈记录中取出传给Do_Error函数的“无名实参”,所用的访问方法完全与图4.2.14和图4.2.15类似。唯一的区别就是对vfprintf而言,不需要通过va_start()来获取format的地址,图4.2.16第14行的函数调用vfprintf()已经把format的地址传给下面的形参ap2。

int vfprintf(FILE *stream, const char*format2, va_list ap2);

图4.2.17给出了当我们进行如下函数调用时内存示意图,从图中我们可以看到,vfprintf的形参ap2已经指向了Do_Error对应栈中的无名参数开始位置,Do_Error的format和vfprintf的format2指向相同的格式化字符串。对vfprintf函数而言,有了格式化字符串的首地址,且又有了无名参数的首地址,七颗龙珠已经凑齐,可以召唤神龙了。

Do_Error(coord, "struct member %sdoesn't exsist", “abc”);

\

图4.2.17 Do_Error的栈示意图

首页 上一页 1 2 下一页 尾页 2/2/2
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇Objective-C单例模式 下一篇C语言记忆化搜索_漫步校园(Hdu 14..

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: