- 论坛徽章:
- 1
|
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位)上做了测试:
- #include <stdio.h>;
- #include <stdlib.h>;
- #include <stdarg.h>;
- void tva(int i, ...);
- int main(void) {
- int i,
- int1,
- int2;
- char ch;
- i = 5;
- int1 = 11;
- int2 = 22;
- ch = 'a';
- tva(i, int1, ch, int2);
- exit(0);
- }
- void tva(int i, ...) {
- int int1;
- char ch;
- int int2;
- va_list ap;
- va_start(ap, i);
- int1 = va_arg(ap, int);
- printf("%d\n", int1);
- ch = va_arg(ap, char); // 问题部分。
- printf("%c\n", ch);
- int2 = va_arg(ap, int);
- printf("%d\n", int2);
- va_end(ap);
- return;
- }
复制代码
在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')
复制代码
运行的时候有段错误,显示:
只有把问题部分的改成ch = va_arg(ap, int),才完全没有问题。这相当与人为的用参数控制内存对齐了。
另外,在这个linux下,直接使用非sparc平台的那种定义(自己手动定义),则出现了如下面win下模拟那样的内存对齐问题!莫非这就是一台x86的平台,只不过是gcc中的宏定义的有问题了?这个问题先放一边,看来不行的话,得联系一下服务器的管理员了
在x86平台的win上,用VC中的CL编译器,以上的写法完全没有问题,也可改成int,丝毫不受影响。
在x86平台的linux上,没有环境测试。不过模拟了一个(^_^,群众的智慧是无敌的!)。在x86平台的主机上,在win下,用VC中的CL编译器,直接使用linux下的那种宏定义方式(自己定义的)。哈哈,果然有内存对齐的问题!
结果如下:
因为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中的结果是:
这是因为TC中把CPU看成16位的,其中int是2个字节,所以是2内存对齐的。
需要注意的是,本文中所说的内存对齐是指栈内存对齐,这和结构体成员的内存对齐是不同的。
现在回到前面留下的那个问题,在网上查了一下,x86是little endian的,而sparc是big endian的!而我又写程序做了一个测试,我用的这台linux服务器是little endian的!就是说,它根本就不是什么sparc服务器!那么对于前面出现的段错误,这个问题就大了。
gcc为什么会编译那段应该在sparc平台上运行的代码呢?是gcc的问题?还是linux的问题? |
|