2012-07-27 63 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