3、C函数可变参数实现细节的一些思考

c函数可变参数很有意思,它和cpu有关系,所以这些参数都是库提供的。 4个参数,va_listva_startva_argva_end ; 以前只会用,并不知道为什么可以这样。

unixwindows系统针对X86平台是这样的:

 #define _INTSIZEOF(n)   ( (sizeof(n) + sizeof(UINTN) - 1) & ~(sizeof(UINTN) - 1) )

 

typedef CHAR8 * va_list;

 #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 )

 

看起来晕吧?哈哈。没有啦,。第一个宏定义是要求4字节对齐,不信可以拿几个数据算算咯。。假设sizeof(UINTN)4 (sizeof(n)5 ,结果为8 

下面就很清楚了,变量定位哈。

 

单片机的就不同了,因为单片机没有对齐的问题。

typedef char *va_list;

#define va_start(ap,v)   ap = (va_list)&v + sizeof(v)

#define va_arg(ap,t)   *(((t *)ap)++)

#define va_end(ap)     ( ap = (va_list)0 )

这个比较明显哈。

 

X86为例:

从汇编的角度来看,函数的参数会入栈,__cdecl是c语言函数默认的调用方式。它会先让最后一个参数入栈,然后堆栈指针sp会根据参数的大小下移(并且按规定的字节数对齐),第一个参数后入栈后,才会让函数内变量入栈。

所以,我们只要确定第一个参数的地址就可以了,后面的参数,可以根据参数的大小确定其地址,然后转换成参数的指针咯。

 

我写了个测试的函数,感觉挺好玩的。

 

typedef struct test

{

 char a;

 int b ;

 char c;

}test;

 void test_f(char *fmt, ...)

{

  printf("the arg &fmt = %u, string is %s, &fmt + 4 = %u\n, ((test *)(&fmt + 4))->dd = %d ,the char is %c ", (unsigned int)(&fmt),*(char **)(&fmt), (unsigned int)(&fmt) + 4, ((test *)((unsigned int) (&fmt) + 4)) ->b, *(char *)((unsigned int)(&fmt) + 16)  );

}

 void *memset(void *d, int c, size_t size )

{

   while( size-- )

  {

     *(char *)d = c ;

    d = (char *)d + 1;

  }

   return (d) ;

}

 

int main(int argc, char *argv[])

{

   test mytest;

  memset(&mytest, 0, sizeof(mytest) );

    mytest.b = 1;

   test_f("es",  mytest, 'c');

   return 0;

}

 运行结果:

the arg &fmt = 3213711168, string is es, &fmt + 4 = 3213711172

, ((test *)(&fmt + 4))->b = 1 ,the char is c

这个结果很有意思,哈哈。结果还说明了,c函数确确实实是按值传递的。前面的函数参数入栈分析没有问题。

 

      下面稍微改一下:

void test_f(char *fmt, ...)

{

  printf("the arg &fmt = %u, string is %s, &fmt + 4 = %u\n, the char is %c , ((test *)(&fmt + 4))->b = %d ", (unsigned int)(&fmt),*(char **)(&fmt), (unsigned int)(&fmt) + 4, *(char *)((unsigned int)(&fmt) + 4), ((test *)((unsigned int) (&fmt) + 8)) ->b  );

}

 int main(int argc, char *argv[])

{

test mytest;

 memset(&mytest, 0, sizeof(mytest) );

  mytest.b = 1;

   test_f("es",  'c', mytest);

   return 0;

}

 运行结果:

the arg &fmt = 3214004096, string is es, &fmt + 4 = 3214004100

, the char is c , ((test *)(&fmt + 4))->b = 1

这个结果说明 x86平台 c语言可变参数函数的参数入栈确实是 4字节对齐的。

 

最后一个疑问:函数指针是否可以作为c语言可变参数函数的参数呢?

c standard貌似讲了这个方面, 结构和函数指针是不允许的,不过c99做了一些改变。 用函数名试试就知道了。

 

typedef void (*pf)(void);

 void f_test(void)

{

    printf("f_test called ! \n" );

}

 

void test_f(char *fmt, ...)

{

  printf("the arg &fmt = %u, string is %s, &fmt + 4 = %u\n, the char is %c , ((test *)(&fmt + 4))->b = %d ", (unsigned int)(&fmt),*(char **)(&fmt), (unsigned int)(&fmt) + 4, *(char *)((unsigned int)(&fmt) + 4) , ((test *)((unsigned int) (&fmt) + 8)) ->b );

 ( *(pf)((unsigned int)(&fmt) + 20))( );

 }

 

主函数只改一个地方:

  test_f("es",  'c', mytest, f_test );

 

运行结果:

the arg &fmt = 3214106528, string is es, &fmt + 4 = 3214106532

 

Command terminated

 

唉,躺着中枪啊!  从汇编上来说,这么做没问题哦。 更奇怪的是,调试时用类似的方法结果也是莫名其妙,我靠!

 

 (gdb) p (f_test)((unsigned int)(&fmt) + 20 )

  , the char is c , ((test *)(&fmt + 4))->b = 1 f_test called !

  $1 = void

(gdb) p (f_test)((unsigned int)(&fmt) + 20 ) ()

  f_test called !

  Invalid data type for function to be called.

(gdb) p (f_test)((unsigned int)(&fmt) + 20 ) (void)

  A syntax error in expression, near `)'.

 

总结: 感觉挺好玩的,哈哈。很可惜现在没有办法验证arm平台的情况,板子不在身边。 应该和X86差不多,有时间可以试试。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值