欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C中va_list在32位和64位机器的区别与差异

程序员文章站 2022-06-25 18:46:42
...

博客搬家,原地址:https://langzi989.github.io/2018/01/01/C中va_list类型在32位和64位机器的区别与使用/

在将程序从32位机器移植到64位机器的过程中经常出现一些奇奇怪怪的错误,这里记录一下在使用可变参数的过程中导致在32位机器上正常运行的程序移植到64位机器上之后出现段错误的发现过程以及解决方案。

首先看下面一段代码:

#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位机器的运行结果如下:

abc
3
abc
3

64位机器运行结果如下:

abc
3
123456
6

原因分析

出现上述结果的原因是由于va_list类型在32位和64位机器的类型不同导致的.

32位va_list

在32位上,va_list的定义为:

//注意,由于中间宏过多,这里省去了中间如_VA_LIST宏,直接给出实际定义。
typedef va_list char**;

64位va_list

在64位上va_list定义为一个结构体数组,并且数组中记录了可变参数被读的偏移量:

// 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即可。

#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://*.com/questions/4958384/what-is-the-format-of-the-x86-64-va-list-structure
http://blog.csdn.net/doubleface999/article/details/55798710