2012-07-27 102 views
3

我的问题vsprintf是我无法直接获取输入参数,我必须先逐个获取输入,并将它们保存在void**中,然后将此void**传递给vsprintf(),这对Windows来说都很好,但是当我来64位linux,gcc无法编译,因为它不允许从void**转换为va_list,有没有人可以给我一些帮助,我应该如何在Linux下做到这一点?在GCC中动态创建va_list - 可以完成吗?

我可以在GCC中动态创建va_list吗?

void getInputArgs(char* str, char* format, ...) 
{ 
    va_list args; 
    va_start(args, format); 
    vsprintf(str, format, args); 
    va_end(args); 
} 

void process(void) 
{ 
    char s[256]; 
    double tempValue; 
    char * tempString = NULL; 
    void ** args_ptr = NULL; 
    ArgFormatType format; //defined in the lib I used in the code 
    int numOfArgs = GetNumInputArgs(); // library func used in my code 

    if(numOfArgs>1) 
    { 
     args_ptr = (void**) malloc(sizeof(char)*(numOfArgs-1)); 
     for(i=2; i<numOfArgs; i++) 
     { 
      format = GetArgType(); //library funcs 

      switch(format) 
      { 
       case ArgType_double: 
        CopyInDoubleArg(i, TRUE, &tempValue); //lib func 
        args_ptr[i-2] = (void*) (int)tempValue;  
        break; 

       case ArgType_char: 
        args_ptr[i-2]=NULL; 
        AllocInCharArg(i, TRUE, &tempString); //lib func 
        args_ptr[i-2]= tempString; 
       break; 
      } 
     } 
    } 

    getInputArgs(s, formatString, (va_list) args_ptr); //Here 
      // is the location where gcc cannot compile, 
      // Can I and how if I can create a va_list myself? 
} 
+0

[vsprintf,使用sprintf获取输入?]的可能重复?(http://stackoverflow.com/questions/11693448/vsprintf-using-sprintf-to-get-inputs) – 2012-07-27 20:32:02

+0

它不是重复的,因为它在这里GCC特有的。 – 2012-07-27 22:45:25

+0

这个问题和[SO 11693448](http://stackoverflow.com/q/11693448)是非常密切相关的,虽然它们并不完全重复。 – 2012-07-27 23:41:02

回答

1

类型的va_list不是void **或任何具有64位gcc相似(在Intel 86/64的机器)。在Mac OS X 10.7.4和RHEL 5上,/usr/include中没有标头stdarg.h。考虑下面的代码:

#include <stdarg.h> 
#include <stdio.h> 
int main(void) 
{ 
    printf("sizeof(va_list) = %zu\n", sizeof(va_list)); 
    return 0; 
} 

在两个RHEL 5和Mac OS X 10.7的输出与64位编译是:

sizeof(va_list) = 24 

使用32位编译,每个平台上的输出是:

sizeof(va_list) = 4 

(你可以认为我很惊讶地发现,32位和64位版本之间这么多的差异,我期待12和24之间的值的32位版本。 )

所以,类型是不透明的;你甚至找不到能够告诉你任何事情的标题;而且它比64位机器上的单个指针大得多。

即使你的代码在某些机器上工作,它仍然远远不能保证在任何地方工作。

GCC 4.7.1手册未提及任何允许您在运行时构建va_list的功能。

+1

'/ usr/include'中缺少'stdarg.h'文件不是问题;这不是编译器搜索头文件的唯一地方。例如,在我的系统中,'#include '带来'/ usr/lib/gcc/x86_64-linux-gnu/4.8/include/stdarg.h'(头文件由gcc提供,而不是由glibc提供)。 'gcc -E'应该告诉你它在你的系统上的位置。我系统中的头文件有'typedef __builtin_va_list __gnuc_va_list;'和'typedef __gnuc_va_list va_list;'。一个快速实验表明它是一个单元素数组类型;当我用'-m32'编译时,它是一个4元素的字节数组或指针。 – 2016-09-29 19:09:14

5

有一种方法可以做到这一点,但它是特定到gcc在Linux上。它可以在32位和64位版本的Linux(测试过)上工作(甚至可以在Windows上工作在32/64位,但在那里是UNTESTED)。

免责声明:我不赞同使用此代码。它不可携带,它是骇人听闻的,并且坦率地说,它是一条谚语式的走钢丝上不稳定的平衡大象。我只是证明可以使用gcc动态创建va_list,这是原始问题所要求的。

这就是说,下面的文章详细介绍了如何使用va_list与amd64 ABI:Amd64 and Va_arg

随着va_list结构的内部结构的知识,我们可以欺骗va_arg宏成从va_list读取我们构建自己:

#if (defined(__linux__) && defined(__x86_64__)) 
// AMD64 byte-aligns elements to 8 bytes 
#define VLIST_CHUNK_SIZE 8 
#else 
#define VLIST_CHUNK_SIZE 4 
#define _va_list_ptr _va_list 
#endif 

typedef struct { 
    va_list _va_list; 
#if (defined(__linux__) && defined(__x86_64__)) 
    void* _va_list_ptr; 
#endif 
} my_va_list; 

void my_va_start(my_va_list* args, void* arg_list) 
{ 
#if (defined(__linux__) && defined(__x86_64__)) 
    /* va_args will read from the overflow area if the gp_offset 
     is greater than or equal to 48 (6 gp registers * 8 bytes/register) 
     and the fp_offset is greater than or equal to 304 (gp_offset + 
     16 fp registers * 16 bytes/register) */ 
    args->_va_list[0].gp_offset = 48; 
    args->_va_list[0].fp_offset = 304; 
    args->_va_list[0].reg_save_area = NULL; 
    args->_va_list[0].overflow_arg_area = arg_list; 
#endif 
    args->_va_list_ptr = arg_list; 
} 

void my_va_end(my_va_list* args) 
{ 
    free(args->_va_list_ptr); 
} 

typedef struct { 
    ArgFormatType type; // OP defined this enum for format 
    union { 
     int i; 
     // OTHER TYPES HERE 
     void* p; 
    } data; 
} va_data; 

现在,我们可以生成va_list指针(它是两者相同的64位和32位版本)使用类似的方法process()或以下:

void* create_arg_pointer(va_data* arguments, unsigned int num_args) { 
    int i, arg_list_size = 0; 
    void* arg_list = NULL; 

    for (i=0; i < num_args; ++i) 
    { 
     unsigned int native_data_size, padded_size; 
     void *native_data, *vdata; 

     switch(arguments[i].type) 
     { 
      case ArgType_int: 
       native_data = &(arguments[i].data.i); 
       native_data_size = sizeof(arguments[i]->data.i); 
       break; 
      // OTHER TYPES HERE 
      case ArgType_string: 
       native_data = &(arguments[i].data.p); 
       native_data_size = sizeof(arguments[i]->data.p); 
       break; 
      default: 
       // error handling 
       continue; 
     } 

     // if needed, pad the size we will use for the argument in the va_list 
     for (padded_size = native_data_size; 0 != padded_size % VLIST_CHUNK_SIZE; padded_size++); 

     // reallocate more memory for the additional argument 
     arg_list = (char*)realloc(arg_list, arg_list_size + padded_size); 

     // save a pointer to the beginning of the free space for this argument 
     vdata = &(((char *)(arg_list))[arg_list_size]); 

     // increment the amount of allocated space (to provide the correct offset and size for next time) 
     arg_list_size += padded_size; 

     // set full padded length to 0 and copy the actual data into the location 
     memset(vdata, 0, padded_size); 
     memcpy(vdata, native_data, native_data_size); 
    } 

    return arg_list; 
} 

最后,我们可以使用它:

va_data data_args[2]; 
data_args[0].type = ArgType_int; 
data_args[0].data.i = 42; 

data_args[1].type = ArgType_string; 
data_args[1].data.p = "hello world"; 

my_va_list args; 
my_va_start(&args, create_arg_pointer(data_args, 2)); 

vprintf("format string %d %s", args._va_list); 

my_va_end(&args); 

而你有它。它的工作原理主要是与正常的va_startva_end宏相同,但允许您传递自己动态生成的字节对齐的指针,而不是依赖调用约定来设置堆栈帧。

+1

'6 gp寄存器* 8位/寄存器':你的意思是字节/寄存器。在下一行中,XMM寄存器为16 *字节*时出现相同的错误。 (顺便说一下,在可变参数函数中获得'__m256'或'__m512'参数是可能的,并且这样做会使gcc生成将'ymm0-7'或'zmm0-7'寄存器保存到堆栈的代码,而不仅仅是'xmm0- 7',如果'al'!= 0) – 2017-11-13 03:53:36

+0

Windows 64位调用约定针对可变参数函数进行了优化(阴影空间允许在堆栈参数创建数组之前溢出寄存器参数),所以我认为它更简单。只有4个参数可以传入寄存器,而不是4个整数和4个FP。 – 2017-11-13 03:59:22

+0

评论更正,谢谢@PeterCordes – 2017-11-13 04:19:11