之前学习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;
    
    }