由于变参宏引发的思考

  • Post author:
  • Post category:其他






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

}







版权声明:本文为skyztttt原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。