共享转贴【一同事的c++学习心得笔记】之三【可变参数探析】

iamcaicainiao 2006-08-24 06:13:10
可变参数探析
在c/c++中函数可以有可变参数,最出名的自然是大名鼎鼎的printf.
int printf(__in_z __format_string const char * _Format, ...); //摘自VC8.0 C library head file
其中的...即是c/c++中的可变参数的语法.
而可变参数是用如下四个宏(Macro)实现的.
va_list
va_start
va_arg
va_end
具体怎么用这四个宏来写带可变参数的函数,请参考任何一本 c 教材.
现摘录C语言开山之作 --- C Language Programming中的一个例子.
#include <stdarg.h>
#include <stdio.h>
void minprintf(char *fmt, ...)
{
va_list ap;
char *p, *sval;
int ival;
double dval;
va_start(ap, fmt);
for(p = fmt; *p; p++)
{
if(*p != '%')
{
putchar(*p);
continue;
}
switch(*++p)
{
case 'd':
ival = va_arg(ap, int);
printf("%d", ival);
break;
case 'f':
dval = va_arg(ap, double);
printf("%f", dval);
break;
case 's':
for(sval = va_arg(ap, char*); *sval; sval++)
{
putchar(*sval);
}
break;
default:
putchar(*p);
break;
}
}
va_end(ap);
}
在VC8.0下添加下面的调用并编译.
int main()
{
minprintf("%s - %d - %f - %d\n", "aaa", 10, 2.34, 3);
return 0;
}
output:
F:\var_len_arg\Debug>var_len_arg.exe
aaa - 10 - 2.340000 - 3
咱们看一下这四个宏的定义.
typedef char * va_list;
va_list 只是 char * 的代名词.
#define va_start _crt_va_start
#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
#define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(v) )
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
代换以下va_start(ap, v) ( ap = ( ( (char *)&reinterpret_cast<const char &>(v) ) + \
( (sizeof(v) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) ) )
v是某个参数,ap = v参数的地址 + v参数的类型的大小(sizeof(v)) ,并对整数地址取整
罗索了这么多,这不就是使ap指向 v 参数的下一个参数的地址吗?
举个例子
va_start(ap, fmt);
fmt是char *, 是指针,即sizeof(char*) = 4
ap = (&fmt + 4 + 3) & 0xFFFFFFFC0;
stack
| |
| ... |
|___________|<--- ap = (&fmt + 4 + 3) & 0xFFFFFFFC0;
| |
| fmt |
|___________|<------- &fmt
| |
| |
#define va_arg _crt_va_arg
#define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
代换以下 va_arg ( *(t *)((ap += ( (sizeof(t) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) \
( (sizeof(t) + sizeof(int) - 1) & ~(sizeof(int) - 1) ) )
举个例子
ival = va_arg(ap, int);
ival = (*(int *)(((ap + sizeof(int)) & 0xFFFFFFFC0) - ((sizeof(int) + 3) & 0xFFFFFFFC0));
一句话,把ap所指向的4 bytes区域解释成int type,并累增ap,使它指向下一个参数地址, 即ival = *ap++;
stack
| |
| 2.34 |
|___________| <--- new ap (va_arg(ap, int)以后)
| |
| 10 | 10 是 int type, 正好占 4 个bytes 空间
|___________| <--- ap = (&fmt + 4 + 3) & 0xFFFFFFFC0;
| |
| fmt |
|___________|<------- &fmt
| |
| |
那么为什么总有烦人的_INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )呢?
两个原因:
1. 每种类型所占空间是不一样的,比如 
sizeof(int) = 4
sizeof(double) = 8
sizeof(char *) = 4
sizeof(char) = 1
sizeof(short) = 2
等等
(sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1)
------
这就是上面画下划线的部分的作用
2. 每个参数的地址必须对齐在 4 字节边界
(sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1)
-------------------
这就是上面画下划线的部分的作用
#define va_end _crt_va_end
#define _crt_va_end(ap) ( ap = (va_list)0 )
等于空,完全可以不需要,估计只是为了与va_start匹配.(最起码在这里VC8.0 C Library中是这样.)
minprintf("%s - %d - %f - %d\n", "aaa", 10, 2.34, 3);
调用的stack如下:
stack
| |
| 3 |
|___________| <--- new ap (va_arg(ap, double)以后)
| |
| |
| | sizeof(double) = 8
| |
| 2.34 |
|___________| <--- new ap (va_arg(ap, int)以后)
| |
| 10 | 10 是 int type, 正好占 4 个bytes 空间
|___________| ap = (&fmt + 4 + 3) & 0xFFFFFFFC0;
| | va_list ap;
| fmt |
|___________| <------- &fmt
| |
| |
上面的解释应该够明白了吧?
如果还有点糊涂,那没办法,只能看 cpu 怎么执行的了!
...全文
896 14 打赏 收藏 转发到动态 举报
写回复
用AI写文章
14 条回复
切换为时间正序
请发表友善的回复…
发表回复
lotusweb 2006-09-19
  • 打赏
  • 举报
回复
mark
joy2th 2006-09-14
  • 打赏
  • 举报
回复
程序员的福音---去www.mylinux.com.cn看看吧,程序员的图书馆
最全面的程序开发资料网www.mylinux.com.cn
包罗java,linux,数据库,安全等等技术资料,更有众多软件外包项目

我们的qq群:15096318 学习程序的都可以来

华资软件作为一家专业的软件公司,现公开承接各种软件外包项目.
www.mylinux.com.cn国内最大的网上软件加工厂,提供最完善的软件外包服务,采用流水型操作流程。

中国软件业的发展不缺人才也不缺资金,缺的是人才的组织和管理,MyLinux平台的建设解决了软件人才的组织和管理问题,将每一项目最合适的软件开发人才以最有效率的形式组织在一起,从而取得1+1〉2的效果。
MyLinux(www. MyLinux.com.cn)由上海巨灵信息技术有限公司主办,是目前国内最大的网上软件加工厂,该网站将提供最完善的软件外包服务,采用流水型操作流程。

详情请登陆www. MyLinux.com.cn
您可把您的具体要求发布在http://www.mylinux.com.cn/guiderAction.do?type=7上,并留下联系方式,我们网站的技术部门和客服会在第一时间审核安排.
蓝色流星 2006-09-02
  • 打赏
  • 举报
回复
mark
swimmer2000 2006-09-01
  • 打赏
  • 举报
回复
楼主写的文章还有点深度,
支持一下.
BOYGUARD110 2006-08-31
  • 打赏
  • 举报
回复
mark
corn8888 2006-08-30
  • 打赏
  • 举报
回复
mark
Arthur_ 2006-08-30
  • 打赏
  • 举报
回复
mk
limlzm 2006-08-30
  • 打赏
  • 举报
回复
mark
seraphimpk 2006-08-30
  • 打赏
  • 举报
回复
mark
playmud 2006-08-25
  • 打赏
  • 举报
回复
这个好不好让老迈来评论一下
iamcaicainiao 2006-08-24
  • 打赏
  • 举报
回复
既然已经彻底搞懂了"可变参数"搞的花头,那扩展以下minprintf()吧!
使得minprintf()支持自定义的class, 用"%z"来表示
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
class CTest
{
public:
explicit CTest()
{
memset(name_, 0, 100);
}
explicit CTest(char *name)
{
strncpy_s(name_, name, 100 - 1);
}
const char *get() const
{
return &name_[0];
}
private:
char name_[100];
};
void minprintf(char *fmt, ...)
{
va_list ap;
char *p, *sval;
int ival;
double dval;
CTest objZ;
const char *tmp;
va_start(ap, fmt);
for(p = fmt; *p; p++)
{
if(*p != '%')
{
putchar(*p);
continue;
}
switch(*++p)
{
case 'd':
ival = va_arg(ap, int);
printf("%d", ival);
break;
case 'f':
dval = va_arg(ap, double);
printf("%f", dval);
break;
case 's':
for(sval = va_arg(ap, char*); *sval; sval++)
{
putchar(*sval);
}
break;
case 'z':
objZ = va_arg(ap, CTest);
tmp = objZ.get();
printf("%s", tmp);
break;
default:
putchar(*p);
break;
}
}
va_end(ap);
}
int main()
{
CTest obj("wzhou");
minprintf("%s - %d - %f - %d - %z\n", "aaa", 10, 2.34, 3, obj);
return 0;
}
Output:
F:\var_len_arg\Debug>var_len_arg.exe
aaa - 10 - 2.340000 - 3 - wzhou
-----
看上去every is OK.
上面的例子仅仅为了演示,实际上printf()一类可变参数的函数是不支持class的,它只支持
输出buildin type,比如int, float, char, short等.
它的根源就是va_arg的实现不支持类.
让我们看一下
case 'z':
objZ = va_arg(ap, CTest);
tmp = objZ.get();
printf("%s", tmp);
break;
va_arg(ap, CTest)到底做了点什么?
; 57 : case 'z':
; 58 : objZ = va_arg(ap, CTest);
mov eax, DWORD PTR _ap$[ebp]
add eax, 100 ; 00000064H //CTest object的大小为100 bytes
mov DWORD PTR _ap$[ebp], eax
mov esi, DWORD PTR _ap$[ebp]
sub esi, 100 ; 00000064H
mov ecx, 25 ; 00000019H
lea edi, DWORD PTR _objZ$[ebp]
rep movsd //直接copy
va_arg 并不知道由于是object, 不应该直接copy,而应该调用object的assignment operator.
由于CTest没有定义assignment operator,c++ compiler给它合成的assignment operator又恰 
恰就是直接copy,这才使得该程序能正确运行.
能否把va_arg改造成支持object呢?这可确实有点技巧?
iamcaicainiao 2006-08-24
  • 打赏
  • 举报
回复
jmp $LN8@minprintf
$LN6@minprintf:
; 25 : case 'f':
; 26 : dval = va_arg(ap, double);
mov eax, DWORD PTR _ap$[ebp] //if argument type is 'f' --- double, ap + 8
add eax, 8 //
mov DWORD PTR _ap$[ebp], eax //
mov ecx, DWORD PTR _ap$[ebp]
fld QWORD PTR [ecx-8] //载入数值协处理器
fstp QWORD PTR _dval$[ebp]
; 27 : printf("%f", dval);
mov esi, esp
sub esp, 8
fld QWORD PTR _dval$[ebp]
fstp QWORD PTR [esp]
push OFFSET ??_C@_02NJPGOMH@?$CFf?$AA@
call DWORD PTR __imp__printf
add esp, 12 ; 0000000cH
cmp esi, esp
call __RTC_CheckEsp
; 28 : break;
jmp SHORT $LN8@minprintf
$LN5@minprintf:
; 29 : case 's':
; 30 : for(sval = va_arg(ap, char*); *sval; sval++)
mov eax, DWORD PTR _ap$[ebp] //if argument is 's' --- char * type, ap + 4
add eax, 4
mov DWORD PTR _ap$[ebp], eax
mov ecx, DWORD PTR _ap$[ebp]
mov edx, DWORD PTR [ecx-4]
mov DWORD PTR _sval$[ebp], edx
jmp SHORT $LN4@minprintf
$LN3@minprintf:
mov eax, DWORD PTR _sval$[ebp]
add eax, 1
mov DWORD PTR _sval$[ebp], eax
$LN4@minprintf:
mov eax, DWORD PTR _sval$[ebp]
movsx ecx, BYTE PTR [eax]
test ecx, ecx
je SHORT $LN2@minprintf
; 31 : {
; 32 : putchar(*sval);
mov eax, DWORD PTR _sval$[ebp]
movsx ecx, BYTE PTR [eax]
mov esi, esp
push ecx
call DWORD PTR __imp__putchar
add esp, 4
cmp esi, esp
call __RTC_CheckEsp
; 33 : }
jmp SHORT $LN3@minprintf
$LN2@minprintf:
; 34 : break;
jmp SHORT $LN8@minprintf
$LN1@minprintf:
; 35 : default:
; 36 : putchar(*p);
mov eax, DWORD PTR _p$[ebp]
movsx ecx, BYTE PTR [eax]
mov esi, esp
push ecx
call DWORD PTR __imp__putchar
add esp, 4
cmp esi, esp
call __RTC_CheckEsp
$LN8@minprintf:
; 37 : break;
; 38 : }
; 39 : }
jmp $LN12@minprintf
$LN11@minprintf:
; 40 : va_end(ap); //这完全是一条空语句,只是把 ap = 0,都用完了,
//赋不赋值无所谓啦
mov DWORD PTR _ap$[ebp], 0
; 41 : }
pop edi
pop esi
pop ebx
add esp, 260 ; 00000104H
cmp ebp, esp
call __RTC_CheckEsp
mov esp, ebp
pop ebp
ret 0
?minprintf@@YAXPADZZ ENDP ; minprintf
iamcaicainiao 2006-08-24
  • 打赏
  • 举报
回复
先看一看assembly code
_TEXT SEGMENT
tv74 = -260 ; size = 4
_dval$ = -60 ; size = 8
_ival$ = -44 ; size = 4
_sval$ = -32 ; size = 4
_p$ = -20 ; size = 4
_ap$ = -8 ; size = 4
_fmt$ = 8 ; size = 4
?minprintf@@YAXPADZZ PROC ; minprintf, COMDAT
; 5 : {
push ebp
mov ebp, esp
sub esp, 260 ; 00000104H
push ebx
push esi
push edi
lea edi, DWORD PTR [ebp-260]
mov ecx, 65 ; 00000041H
mov eax, -858993460 ; ccccccccH
rep stosd
; 6 : va_list ap;
; 7 : char *p, *sval;
; 8 : int ival;
; 9 : double dval;
; 10 :
; 11 : va_start(ap, fmt);
lea eax, DWORD PTR _fmt$[ebp+4] //_fmt = 8, _fmt$[ebp+4]= [ebp + 12], eax = ebp + 12
//ebp指向stack frame
stack
| |
| 3 |
|___________| <--- ebp + 20, minprintf函数的第三个参数
| |
| |
| |
| | sizeof(double) = 8 bytes
| 2.34 |
|___________|
| | sizeof(int) = 4 bytes
| 10 | 
|___________| <--- ebp + 12, minprintf函数的第二个参数
| |
| fmt | sizeof(char *) = 4 bytes
|___________| <--- ebp + 8, minprintf函数的第一个参数
| |
|return addr| minprintf()的返回地址
|___________|
| |
| old ebp | original ebp value, 指向上层函数的stack frame,在这里是main函数的stack frame
|___________| <--- ebp
| |
| |
|___________| <--- ebp - 4 minprintf函数的local variables
| |
| ap |
|___________| <--- ebp - 8
| |
| ... |
mov DWORD PTR _ap$[ebp], eax // ap = &fmt, ap = ebp + 12
; 12 : for(p = fmt; *p; p++)
mov eax, DWORD PTR _fmt$[ebp]
mov DWORD PTR _p$[ebp], eax
jmp SHORT $LN13@minprintf
$LN12@minprintf:
mov eax, DWORD PTR _p$[ebp]
add eax, 1
mov DWORD PTR _p$[ebp], eax
$LN13@minprintf:
mov eax, DWORD PTR _p$[ebp]
movsx ecx, BYTE PTR [eax]
test ecx, ecx
je $LN11@minprintf
; 13 : {
; 14 : if(*p != '%')
mov eax, DWORD PTR _p$[ebp]
movsx ecx, BYTE PTR [eax]
cmp ecx, 37 ; 00000025H
je SHORT $LN10@minprintf
; 15 : {
; 16 : putchar(*p);
mov eax, DWORD PTR _p$[ebp]
movsx ecx, BYTE PTR [eax]
mov esi, esp
push ecx
call DWORD PTR __imp__putchar
add esp, 4
cmp esi, esp
call __RTC_CheckEsp
; 17 : continue;
jmp SHORT $LN12@minprintf
$LN10@minprintf:
; 18 : }
; 19 : switch(*++p)
mov eax, DWORD PTR _p$[ebp]
add eax, 1
mov DWORD PTR _p$[ebp], eax
mov ecx, DWORD PTR _p$[ebp]
mov dl, BYTE PTR [ecx]
mov BYTE PTR tv74[ebp], dl
cmp BYTE PTR tv74[ebp], 100 ; 00000064H
je SHORT $LN7@minprintf
cmp BYTE PTR tv74[ebp], 102 ; 00000066H
je SHORT $LN6@minprintf
cmp BYTE PTR tv74[ebp], 115 ; 00000073H
je SHORT $LN5@minprintf
jmp $LN1@minprintf
$LN7@minprintf:
; 20 : {
; 21 : case 'd':
; 22 : ival = va_arg(ap, int);
mov eax, DWORD PTR _ap$[ebp] //if argument type is 'd' --- int, ap + 4
add eax, 4 //
mov DWORD PTR _ap$[ebp], eax //
mov ecx, DWORD PTR _ap$[ebp]
mov edx, DWORD PTR [ecx-4]
mov DWORD PTR _ival$[ebp], edx
; 23 : printf("%d", ival);
mov esi, esp
mov eax, DWORD PTR _ival$[ebp]
push eax
push OFFSET ??_C@_02DPKJAMEF@?$CFd?$AA@
call DWORD PTR __imp__printf
add esp, 8
cmp esi, esp
call __RTC_CheckEsp
; 24 : break;

64,652

社区成员

发帖
与我相关
我的任务
社区描述
C++ 语言相关问题讨论,技术干货分享,前沿动态等
c++ 技术论坛(原bbs)
社区管理员
  • C++ 语言社区
  • encoderlee
  • paschen
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
  1. 请不要发布与C++技术无关的贴子
  2. 请不要发布与技术无关的招聘、广告的帖子
  3. 请尽可能的描述清楚你的问题,如果涉及到代码请尽可能的格式化一下

试试用AI创作助手写篇文章吧