C中va_list在32位和64位机器的区别与差异
在将程序从32位机器移植到64位机器的过程中经常出现一些奇奇怪怪的错误,这里记录一下在使用可变参数的过程中导致在32位机器上正常运行的程序移植到64位机器上之后出现段错误的发现过程以及解决方案。
首先看下面一段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include <iostream> #include <stdio.h> #include <stdarg.h> #include <string.h> void parse(va_list ap) { char* arg; arg = va_arg(ap, char*); std::cout << arg << std::endl << strlen(arg) << std::endl; } void test(const char* format, ...) { va_list ap; va_start(ap, format); for (int i = 0; i < 2; i++) { parse(ap); } va_end(ap); } int main() { test("hget %s %s", "abc", "123456"); }
|
在32位机器的运行结果如下:
在64位机器运行结果如下:
原因分析
出现上述结果的原因是由于va_list类型在32位和64位机器的类型不同导致的.
32位va_list
在32位上,va_list的定义为:
1 2
| typedef va_list char**;
|
64位va_list
在64位上va_list定义为一个结构体数组,并且数组中记录了可变参数被读的偏移量:
1 2 3 4 5 6 7
| // Figure 3.34 typedef struct { unsigned int gp_offset; unsigned int fp_offset; void *overflow_arg_area; void *reg_save_area; } va_list[1];
|
程序异常分析
当在32位机器上将va_list(char**)作为参数传递给函数的时候,该函数将从头开始读取该变长参数,还是使用va_list完毕并不记录当前va_list被读的偏移量,所以当第二次传入该va_list还是从头开始读取。
当在64为机器上将va_list(struct 数组)作为参数传递给函数的时候,该函数读取va_list完毕之后,将读取的偏移量记录在结构体中,由于其为数组传入函数,所以该被调用的函数改变了传入的va_list的偏移量。导致下次调用该函数从记录的偏移量开始读,造成不可预测或者内存越界等问题。
移植解决方案
将va_list初始化写到for循环内部,每次调用函数前都初始化va_list即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include <iostream> #include <stdio.h> #include <stdarg.h> #include <string.h> void parse(va_list ap) { char* arg; arg = va_arg(ap, char*); std::cout << arg << std::endl << strlen(arg) << std::endl; } void test(const char* format, ...) { for (int i = 0; i < 2; i++) { va_list ap; va_start(ap, format); parse(ap); va_end(ap); } } int main() { test("hget %s %s", "abc", "123456"); }
|
参考:
https://stackoverflow.com/questions/4958384/what-is-the-format-of-the-x86-64-va-list-structure
http://blog.csdn.net/doubleface999/article/details/55798710
Comment and share