C语言中可变参数宏的深入讨论

C语言中可变参数宏的深入讨论

aero 发表于 2004年10月13日18时11分
C语言中可变参数宏的深入讨论
C语言中的可变参数是用va_list等几个宏来实现的。其原理就是获取参数进栈的地址,然后分析出各个参数。具体的用法不在赘述,其实也很简单。看下面的例子应该就可以掌握。
VC中IX86平台的:
#ifndef _VA_LIST_DEFINED
#ifdef _M_ALPHA
typedef struct {
        char *a0;       /* pointer to first homed integer argument */
        int offset;     /* byte offset of next parameter */
} va_list;
#else
typedef char *  va_list;
#endif
#define _VA_LIST_DEFINED
#endif
#ifdef  _M_IX86

#define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap)      ( ap = (va_list)0 )
#elif   defined(_M_MRX000)
……
还有很多对其他平台的支持,比如IMR000、ALPHA、PPC、M68K、MPPC等各种平台的支持。大家可以看到,在VC中的这个实现,特别注意了内存的对齐。
下面是TC2.0下的这几个宏的实现:
#if !defined(__STDARG)
#define __STDARG
typedef void *va_list;
#define va_start(ap, parmN) (ap = ...)
#define va_arg(ap, type) (*((type *)(ap))++)
#define va_end(ap)
#define _va_ptr   (...)
#endif
呵呵,TC中的写法很简陋呢!

而下面是linux下对这几个宏的实现。很简单,整个文件也就下面这些句:
#ifndef __STDARG_H
#define __STDARG_H
#ifdef sparc
#  define _VA_ALIST_            "__builtin_va_alist"
   typedef char *va_list;
#  define va_start(ap, p)       (ap = (char *) &__builtin_va_alist)
#  define va_arg(ap, type)      ((type *) __builtin_va_arg_incr((type *) ap))[0]
#  define va_end(ap)
#else /* vax, mc68k, 80*86 */
   typedef char *va_list;
#  define va_start(ap, p)       (ap = (char *) (&(p)+1))
#  define va_arg(ap, type)      ((type *) (ap += sizeof(type)))[-1]
#  define va_end(ap)
#endif
#endif /* __STDARG_H */
#if __FIRST_ARG_IN_AX__
#error First arg is in a register, stdarg.h cannot take its address
#endif
只区分了sparc平台和其他平台。sparc平台的那个内建函数还没看到源代码,但是看其他平台的代码,没有对齐的考虑。linux下的其他平台为什么没有对齐机制呢?这样的宏定义能很好的工作吗?当出现需要对齐的地方怎么办?
因为测试下面的程序的时候,在我用的linux系统上,我用#include <stdarg.h>的方法,用gcc的-E参数预编译了后,代码是使用"__builtin_va_alist"的。所以我暂时认定所使用的这个linux是装在sparc平台上的。(后经证实是错误的)
为此,就下面的程序在sparc和x86两平台(32位)上做了测试:
在sparc平台上的linux上,用gcc编译的时候会有warning:
test.c: In function `tva':
test.c:35: warning: `char' is promoted to `int' when passed through `...'
test.c:35: warning: (so you should pass `int' not `char' to `va_arg')
运行的时候有段错误,显示:
11
Segmentation fault
只有把问题部分的改成ch = va_arg(ap, int),才完全没有问题。这相当与人为的用参数控制内存对齐了。
另外,在这个linux下,直接使用非sparc平台的那种定义(自己手动定义),则出现了如下面win下模拟那样的内存对齐问题!莫非这就是一台x86的平台,只不过是gcc中的宏定义的有问题了?这个问题先放一边,看来不行的话,得联系一下服务器的管理员了:-(
在x86平台的win上,用VC中的CL编译器,以上的写法完全没有问题,也可改成int,丝毫不受影响。
在x86平台的linux上,没有环境测试。不过模拟了一个(^_^,群众的智慧是无敌的!)。在x86平台的主机上,在win下,用VC中的CL编译器,直接使用linux下的那种宏定义方式(自己定义的)。哈哈,果然有内存对齐的问题!
结果如下:
11
a
369098752
因为cl是32位的编译器,所以默认的是4内存对齐。
用16进制表示x86的内存:
---------------------------------------------------
| 11                   a                   22             |
| 0B 00 00 00    61 00 00 00    16 00 00 00 |
| ^                   ^   ^              ^               |
| 1                    2   3               4               |
---------------------------------------------------
如上图,当考虑内存对齐的时候,取第三个参数的时候,指针的位置是在4的地方,而如果不考虑内存对齐,则是在3的地方。如果在3的地方,则第三个参数就取成0x16000000即是369098752了。
x86平台,win下TC2.0中的结果是:
11
a
5632
这是因为TC中把CPU看成16位的,其中int是2个字节,所以是2内存对齐的。
需要注意的是,本文中所说的内存对齐是指栈内存对齐,这和结构体成员的内存对齐是不同的。
现在回到前面留下的那个问题,在网上查了一下,x86是little endian的,而sparc是big endian的!而我又写程序做了一个测试,我用的这台linux服务器是little endian的!就是说,它根本就不是什么sparc服务器!那么对于前面出现的段错误,这个问题就大了。
gcc为什么会编译那段应该在sparc平台上运行的代码呢?是gcc的问题?还是linux的问题?
阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页