之前学习c语言就知道了va_start,
va_arg,
va_end这三个宏,记得当时是为了自己实现printf函数,最近有空又细看了下其实现。
32位(编译器)和64位(编译器)下进行一个实验,这个实验将揭示变参宏的本质,同时也提出一个32位与64位编译器差异的问题:
首先看如下代码
#include <stdio.h>
void fun(int a, …)
{
int *tmp = &a;
int i = 0;
for (; i < a; ++i) {
++tmp;
printf(“%d\t”, *tmp);
}
printf(“\n”);
}
int main()
{
fun(4, 1, 2, 3, 4);
return 0;
}
测试环境为WIN7,当采用不同编译器,分别打开Visual Studio 2008 Command Prompt(32 位), Vistual Studio 2008 x64 Win64 Command Prompt(64位)。
源程序保存为1.c,分别用两种编译器输入命令cl 1.c,最终生成1.exe。
最终输出结果为:
1 2 3 4(32位编译器)
0 1 0 2(64位编译器)(注:如果将循环中a改为8,则显示为 0 1 0 2 0 3 0 4,待会将解释其中意义)
从上面实验可以知道32位编译器是以4字节进行入栈,而64位编译器将以8字节入栈。具体原因可从源代码中看出:
查看vs中include中的stdarg.h发现如下一段话:
#if !defined(_WIN32)
#error ERROR: Only Win32 target supported!
#endif
起初以为此段话的意思为只支持32位机器或者说是32位编译器,后来测试后发现,其实不然,虽然64位机器函数调用时,由于其寄存器数目较多,可以不采用堆栈方式进行参数传递,但是后来查阅资料,只要是有变参宏的一定以堆栈进行传递,这样64位机器与32位机器便无差异了。
以下一个实验来验证_WIN32的意义:
#include <stdio.h>
int main()
{
#ifdef _WIN32
printf(“define _WIN32\n”);
#endif
#ifdef _WIN64
printf(“define _WIN64\n”);
#endif
printf(“over \n”);
return 0;
}
将以上代码分别在32位,64位编译器中进行编译并运行,输出结果如下:
32: define _WIN32
over
64: define _WIN32
define _WIN64
over
根据以上估计,_WIN32、_WIN64表示支持的位数,即64位机器既能支持64位,也能兼容32位,故其定义了两个宏。
以上解决了32位机器与64位机器的问题,还没有具体讲到变参宏的具体实现,接着查看vs头文件。
#define va_start _crt_va_start
#define va_arg _crt_va_arg
#define va_end _crt_va_end
#elif defined(_M_IX86)
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) – 1) & ~(sizeof(int) – 1) )
#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
#define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) – _INTSIZEOF(t)) )
#define _crt_va_end(ap) ( ap = (va_list)0 )
#elif defined(_M_IA64)
#ifdef __cplusplus
extern void __cdecl __va_start(va_list*, …);
#define _crt_va_start(ap,v) ( __va_start(&ap, _ADDRESSOF(v), _SLOTSIZEOF(v), \
_ADDRESSOF(v)) )
#else
#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _SLOTSIZEOF(v) )
#endif
#define _crt_va_arg(ap,t) (*(t *)((ap += _SLOTSIZEOF(t)+ _APALIGN(t,ap)) \
-_SLOTSIZEOF(t)))
#define _crt_va_end(ap) ( ap = (va_list)0 )
#elif defined(_M_AMD64)
……
#ifdef __cplusplus
#define _ADDRESSOF(v) ( &reinterpret_cast<const char &>(v) )
#else
#define _ADDRESSOF(v) ( &(v) )
#endif
#if defined(_M_IA64) && !defined(_M_CEE_PURE)
#define _VA_ALIGN 8
#define _SLOTSIZEOF(t) ( (sizeof(t) + _VA_ALIGN – 1) & ~(_VA_ALIGN – 1) )
#define _VA_STRUCT_ALIGN 16
#define _ALIGNOF(ap) ((((ap)+_VA_STRUCT_ALIGN – 1) & ~(_VA_STRUCT_ALIGN -1)) \
– (ap))
#define _APALIGN(t,ap) (__alignof(t) > 8 ? _ALIGNOF((uintptr_t) ap) : 0)
#else
#define _SLOTSIZEOF(t) (sizeof(t))
#define _APALIGN(t,ap) (__alignof(t))
#endif
以上代码有些多,取va_start进行研究:即研究_crt_va_start;
重点看这个宏,是64位编译的宏#define _crt_va_start(ap,v) ( __va_start(&ap, _ADDRESSOF(v), _SLOTSIZEOF(v), _ADDRESSOF(v)) ),主要关系到_ADDRESSOF和_SLOTSIZEOF。
可以卡看到宏_ADDRESSOF只是取当前单元的地址,32位于64位除了指针长度的不同其余无太大差别;再看_SLOTSIZEOF,#if defined(_M_IA64) && !defined(_M_CEE_PURE)情况下为:
#define _VA_ALIGN 8
#define _SLOTSIZEOF(t) ( (sizeof(t) + _VA_ALIGN – 1) & ~(_VA_ALIGN – 1) )
可以看到此单元至少为8字节对齐的,而另一种情况为#define _SLOTSIZEOF(t) (sizeof(t)),以当前t的长度对齐,从这点就可以解释开头代码的结果了(本文开头那段代码只是int * 的++,只增加4字节,所以32位、64位编译器编译出的可执行程序发生上述差异)。
以上说了这么多,只是解释了变参宏在32位于64位编译环境下的不同现象,以及原因(虽然可能这些对于程序员来说是透明的),再来说说其具体实现,就只说说32位编译吧,这些baidu中比比皆是,就简略写写吧:
typedef char * va_list;
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) – 1) & ~(sizeof(int) – 1) )
#define _crt_va_start(ap,v) ( ap = (va_list)_ADDRESSOF(v) + _INTSIZEOF(v) )
#define _crt_va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) – _INTSIZEOF(t)) )
#define _crt_va_end(ap) ( ap = (va_list)0 )
首先_INTSIZEOF为字节对齐,可以看到字节将以int进行对齐,即4字节对齐,va_start将ap指针指向v以后的那个单元,va_arg将ap指针指向下一个单元,并返回原先单元所表示的值,va_end则将ap指针清零。
还有一点小疑惑,希望各位知道问题所在的不吝赐教:
void demo(int begin, …)
{
//1
va_list arg;
//2
va_start(arg, begin);
int i = 0;
for (; i < begin; ++i) {
printf(“address: %p, %d\n”, arg, va_arg(arg, int));
}
va_end(arg);
}
int main()
{
demo(4, 1, 2, 3, 4);
return 0;
}
即只要用了变参宏,之后再定义变量,必定报错,不管32位还是64位编译器,如上代码,只要将i的定义放到1或者2处将能正常编译通过。
另外,之前曾baidu到一个demo,觉得有些问题,以下,是我修正后的代码,自测能用
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
int demo(char msg, …)
{
va_list arg;
int argNo = 0;
char *para;
va_start(arg, msg);
while(1) {
para = va_arg(arg, char*);
if (strcmp(para, “”) == 0) {
break;
}
printf(“parameter #%d is: %s\n”, argNo, para);
++argNo;
}
va_end(arg);
return 0;
}
int main() {
demo(‘D’, “This”, “is”, “a”, “demo!”, “”);
return 0;
}