免费注册 查看新帖 |

ChinaUnix.net

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 9768 | 回复: 8

[C] C语言中可变参数宏的深入讨论 [复制链接]

论坛徽章:
1
荣誉版主
日期:2011-11-23 16:44:17
发表于 2004-10-12 13:36 |显示全部楼层
C语言中的可变参数是用va_list等几个宏来实现的。其原理就是获取参数进栈的地址,然后分析出各个参数。具体的用法不在赘述,其实也很简单。看下面的例子应该就可以掌握。

VC中IX86平台的:

  1. #ifndef _VA_LIST_DEFINED
  2. #ifdef  _M_ALPHA
  3. typedef struct {
  4.         char *a0;       /* pointer to first homed integer argument */
  5.         int offset;     /* byte offset of next parameter */
  6. } va_list;
  7. #else
  8. typedef char *  va_list;
  9. #endif
  10. #define _VA_LIST_DEFINED
  11. #endif

  12. #ifdef  _M_IX86


  13. #define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

  14. #define va_start(ap,v)  ( ap = (va_list)&v + _INTSIZEOF(v) )
  15. #define va_arg(ap,t)    ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
  16. #define va_end(ap)      ( ap = (va_list)0 )

  17. #elif   defined(_M_MRX000)
  18. ……

复制代码

还有很多对其他平台的支持,比如IMR000、ALPHA、PPC、M68K、MPPC等各种平台的支持。大家可以看到,在VC中的这个实现,特别注意了内存的对齐。

下面是TC2.0下的这几个宏的实现:

  1. #if        !defined(__STDARG)
  2. #define __STDARG

  3. typedef void        *va_list;

  4. #define va_start(ap, parmN)        (ap = ...)
  5. #define va_arg(ap, type)        (*((type *)(ap))++)
  6. #define va_end(ap)
  7. #define _va_ptr                        (...)
  8. #endif

复制代码

呵呵,TC中的写法很简陋呢!


而下面是linux下对这几个宏的实现。很简单,整个文件也就下面这些句:

  1. #ifndef __STDARG_H
  2. #define __STDARG_H

  3. #ifdef sparc
  4. #  define _VA_ALIST_            "__builtin_va_alist"
  5.    typedef char *va_list;
  6. #  define va_start(ap, p)       (ap = (char *) &__builtin_va_alist)
  7. #  define va_arg(ap, type)      ((type *) __builtin_va_arg_incr((type *) ap))[0]
  8. #  define va_end(ap)
  9. #else /* vax, mc68k, 80*86 */
  10.    typedef char *va_list;
  11. #  define va_start(ap, p)       (ap = (char *) (&(p)+1))
  12. #  define va_arg(ap, type)      ((type *) (ap += sizeof(type)))[-1]
  13. #  define va_end(ap)
  14. #endif

  15. #endif /* __STDARG_H */

  16. #if __FIRST_ARG_IN_AX__
  17. #error First arg is in a register, stdarg.h cannot take its address
  18. #endif

复制代码

只区分了sparc平台和其他平台。sparc平台的那个内建函数还没看到源代码,但是看其他平台的代码,没有对齐的考虑。linux下的其他平台为什么没有对齐机制呢?这样的宏定义能很好的工作吗?当出现需要对齐的地方怎么办?

因为测试下面的程序的时候,在我用的linux系统上,我用#include <stdarg.h>;的方法,用gcc的-E参数预编译了后,代码是使用"__builtin_va_alist"的。所以我暂时认定所使用的这个linux是装在sparc平台上的。(后经证实是错误的)

为此,就下面的程序在sparc和x86两平台(32位)上做了测试:


  1. #include <stdio.h>;
  2. #include <stdlib.h>;
  3. #include <stdarg.h>;

  4. void tva(int i, ...);

  5. int main(void) {
  6.         int     i,
  7.                 int1,
  8.                 int2;

  9.         char    ch;

  10.         i = 5;
  11.         int1 = 11;
  12.         int2 = 22;
  13.         ch = 'a';

  14.         tva(i, int1, ch, int2);

  15.         exit(0);
  16. }

  17. void tva(int i, ...) {
  18.         int     int1;
  19.         char    ch;
  20.         int     int2;

  21.         va_list ap;

  22.         va_start(ap, i);
  23.         int1 = va_arg(ap, int);
  24.         printf("%d\n", int1);
  25.         ch = va_arg(ap, char);        // 问题部分。
  26.         printf("%c\n", ch);
  27.         int2 = va_arg(ap, int);
  28.         printf("%d\n", int2);

  29.         va_end(ap);
  30.         return;
  31. }

复制代码

在sparc平台上的linux上,用gcc编译的时候会有warning:

  1. test.c: In function `tva':
  2. test.c:35: warning: `char' is promoted to `int' when passed through `...'
  3. test.c:35: warning: (so you should pass `int' not `char' to `va_arg')
复制代码


运行的时候有段错误,显示:

  1. 11
  2. Segmentation fault
复制代码


只有把问题部分的改成ch = va_arg(ap, int),才完全没有问题。这相当与人为的用参数控制内存对齐了。

另外,在这个linux下,直接使用非sparc平台的那种定义(自己手动定义),则出现了如下面win下模拟那样的内存对齐问题!莫非这就是一台x86的平台,只不过是gcc中的宏定义的有问题了?这个问题先放一边,看来不行的话,得联系一下服务器的管理员了

在x86平台的win上,用VC中的CL编译器,以上的写法完全没有问题,也可改成int,丝毫不受影响。

在x86平台的linux上,没有环境测试。不过模拟了一个(^_^,群众的智慧是无敌的!)。在x86平台的主机上,在win下,用VC中的CL编译器,直接使用linux下的那种宏定义方式(自己定义的)。哈哈,果然有内存对齐的问题!
结果如下:

  1. 11
  2. a
  3. 369098752
复制代码


因为cl是32位的编译器,所以默认的是4内存对齐。
用16进制表示x86的内存:

  1. -------------------------------------------------------
  2. | 11                   a                     22                |
  3. | 0B 00 00 00    61 00 00 00    16 00 00 00 |
  4. | ^              ^    ^                     ^                 |
  5. | 1              2     3                      4                 |
  6. -------------------------------------------------------
复制代码

如上图,当考虑内存对齐的时候,取第三个参数的时候,指针的位置是在4的地方,而如果不考虑内存对齐,则是在3的地方。如果在3的地方,则第三个参数就取成0x16000000即是369098752了。

x86平台,win下TC2.0中的结果是:

  1. 11
  2. a
  3. 5632
复制代码


这是因为TC中把CPU看成16位的,其中int是2个字节,所以是2内存对齐的。

需要注意的是,本文中所说的内存对齐是指栈内存对齐,这和结构体成员的内存对齐是不同的。

现在回到前面留下的那个问题,在网上查了一下,x86是little endian的,而sparc是big endian的!而我又写程序做了一个测试,我用的这台linux服务器是little endian的!就是说,它根本就不是什么sparc服务器!那么对于前面出现的段错误,这个问题就大了。

gcc为什么会编译那段应该在sparc平台上运行的代码呢?是gcc的问题?还是linux的问题?

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
发表于 2004-10-12 14:30 |显示全部楼层

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

收藏

论坛徽章:
0
发表于 2004-10-12 15:34 |显示全部楼层

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

钻研精神真值得我学习.
俺是拿来就用,没考虑过其他.

后面那问题既不是GCC也不是LINUX的错误吧
头文件里不是有预编译选项嘛,那里有些问题吧.

论坛徽章:
1
荣誉版主
日期:2011-11-23 16:44:17
发表于 2004-10-12 15:38 |显示全部楼层

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

是啊,可是那个预编译选项是谁来打开的呢?

论坛徽章:
0
发表于 2004-10-12 15:55 |显示全部楼层

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

以前从来没考虑过
刚才找了半天,没找到.
会不会gcc使用-D选项来定义的.
纯粹瞎猜,期待真的明白人士.

论坛徽章:
1
荣誉版主
日期:2011-11-23 16:44:17
发表于 2004-10-12 16:03 |显示全部楼层

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

关键是,对于这种预编译选项应该是系统来完成的啊。

论坛徽章:
0
发表于 2004-10-12 16:07 |显示全部楼层

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

我记得去下载GCC安装包的时候各种平台是有区分的.
会不会不同平台的版本里就已经定义了不同的宏.

论坛徽章:
1
荣誉版主
日期:2011-11-23 16:44:17
发表于 2004-10-13 09:24 |显示全部楼层

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

顶上去,让大家在看看。

论坛徽章:
1
CU十二周年纪念徽章
日期:2013-10-24 15:41:34
发表于 2010-08-16 10:03 |显示全部楼层
挖个坟
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

基于案例的 SQL 优化实战训练营

讲师:中电福富特级专家梁敬彬,参与本次课程培训,你将收获:
1. 能编写出较为高效的 SQL;
2. 能解决70%以上的数据库常见优化问题;
3. 能得到老师提供的高效的相关工具和解决方案;
4. 能举一反三,收获不仅仅是 SQL 优化。
现在购票享受8.8折优惠!
----------------------------------------
优惠时间:2019年3月20日前

大会官网>>
  

北京盛拓优讯信息技术有限公司. 版权所有 16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122
中国互联网协会会员  联系我们:huangweiwei@it168.com
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP