2010-07-09 73 views
5

我有一個C編程問題:我想寫一個帶有可變參數列表的函數,其中每個參數的具體類型都不知道 - 僅以字節爲單位。這意味着,如果我想獲得一個int參數,我(在某個地方之前)定義:將會有一個sizeof(int)的參數,用一個回調函數xyz處理。帶有未知類型名稱的va_list - 只知道字節大小!

我的變量參數函數現在應該從其調用收集所有信息,真正的數據類型特定操作(也可以是用戶定義的數據類型)只能通過回調函數進行處理。

在標準的va_arg函數中,不可能說「給我一個來自我的參數列表的X字節值」,所以我想這樣做。我的數據類型在這種情況下是雙倍的,但它可以是任何其他數量的字節(甚至可變的)。

#include <stdlib.h> 
#include <stdio.h> 
#include <stdarg.h> 

int fn(int anz, ...) 
{ 
     char* p; 
     int    i; 
     int    j; 
     va_list args; 
     char* val; 
     char* valp; 
     int    size = sizeof(double); 

     va_start(args, anz); 

     val = malloc(size); 

     for(i = 0; i < anz; i++) 
     { 
       memcpy(val, args, size); 
       args += size; 

       printf("%lf\n", *((double*)val)); 
     } 

     va_end(args); 
} 

int main() 
{ 
     fn(1, (double)234.2); 
     fn(3, (double)1234.567, (double)8910.111213, (double)1415.161718); 

     return 0; 
} 

它適用於我在Linux(gcc)下。但我現在的問題是:這真的是可移植的嗎?還是會在其他系統和編譯器下失敗?

我的另一種方法是更換

   memcpy(val, args, size); 
       args += size; 

  for(j = 0; j < size; j++) 
        val[j] = va_arg(args, char); 

但後來,我的價值觀出了問題。

任何想法或幫助嗎?

+2

您不能提前決定您計劃通過的所有數據類型嗎?然後爲每一個分配一個參數。將第一個參數保留爲int,然後指示類型。函數HAS的調用者知道傳遞哪個數據類型。更傳統的做法是傳遞void *和int參數,然後根據int(或enum)將void *參數轉換爲正確的數據類型。 – 2010-07-09 14:11:40

+0

這是我想要避免的。我的數據類型是已知的,但只有在用戶特定的回調函數中才能處理這些數據。我想要實現的通用函數應該只在數據對象定義的內存中構建一個新的數據對象。這個定義(它是屬性聲明的數組)只有關於每個變量大小的信息。 – Jan 2010-07-09 14:23:42

回答

1

它不便攜式,對不起。 va_list的格式依賴於編譯器/平臺。

您必須使用va_arg()訪問va_list,並且必須將正確類型的參數傳遞給va_list。

但是,我認爲如果您將正確大小的類型傳遞給va_arg,這可能會奏效。即。類型通常不相關,只有它的大小。但是,即使這不能保證適用於所有系統。

我想我會建議重新看你的設計,看看如果你可以找到另一種設計 - 有更多的細節,你爲什麼試圖做到這一點,你可以分享?你可以將va_list傳遞給回調嗎?

更新

究其原因,逐字節的方法是行不通的可能是相當複雜。就C標準而言,它不起作用的原因是因爲它不被允許 - 你只能使用va_arg來訪問傳遞給函數的相同類型。

但我懷疑你想知道這是怎麼回事幕後:)

第一個原因是,當你讀通過了「字符」的功能,它實際上是自動提升爲int,所以作爲int存儲到va_arg中。所以當你讀一個字符時,你正在讀取一個「int」值的內存,而不是「char」 - 所以你實際上並不是每次都讀一個字節。

另一個原因是與對齊有關 - 在某些體系結構上(一個例子是非常新的ARM處理器),「double」必須與64位(有時甚至是128位)邊界對齊。也就是說,對於指針值p,p%16(p模數16,以字節爲單位 - 即。128位)必須等於0.所以,當這些打包在va_arg上時,編譯器可能會確保任何雙精度值都有空格(填充),因此它們只能在正確對齊的情況下發生 - 但是您沒有考慮它當你一次讀入一個字節的條目時。

(可能還有其他原因,太 - 我不十分熟悉的va_arg內的作品。)

+0

嗯......將va_list傳遞給一個特殊的回調函數將是一個選項。然後,我必須將其聲明爲 va_list get_value(va_list l,char * buf) 其中,buf是將數據寫入/複製到的內存。 但爲什麼我的第二種方法無法工作 - 將每字節逐字節複製到內存緩衝區中? – Jan 2010-07-09 14:29:39

+0

我已經更新了我的答案,試圖解釋爲什麼字節每字節不起作用。 – JosephH 2010-07-09 17:45:40

+0

我希望這個*最近的ARM *平臺上的對齊規則只是必須在8 *字節*邊界(這將是64位)上對齊一個'double'。 – 2011-11-15 07:36:37

0

在這種情況下,避免va_args很可能是清潔,因爲你最終仍綁定到特定數量的代碼中的參數在調用點。我會去傳遞一系列參數。

struct arg 
    { 
    void* vptr; 
    size_t len; 
    }; 

    void fn(struct arg* args, int nargs); 

對於這個問題,我還承載數據的定義也是如此,無論是一個int早些時候在評論或指針提到一個struct,如果它是一個更復雜的數據清晰度。

+0

你將如何去序列化數據定義? – 2010-07-09 14:53:29

+0

我想要的數據對象的創建儘可能簡單: OBJ_DESC obj_desc [] = { 的sizeof(int)的,的sizeof(字符*),的sizeof(雙),的sizeof(INT *) }; 鹿角後,我創建 一個對象來創建(obj_desc,1,「Hello World」的,566.2,&x); 一切背後obj_desc從obj_desc自身攝取。創建參數數組首先將太多的開銷。 – Jan 2010-07-09 14:59:50

+0

我猜測你有一個數據定義類型系統的原因是創建/操作需要數據驅動 - 否則爲什麼不使用普通的結構。因此,你將多久用一次文字代碼創建對象,系統將使用? – Digikata 2010-07-09 15:46:09

1

va_list上執行算術運算是非易失性的極端。您應該使用va_arg,通常使用與參數大小相同的類型,並且可能是可以在任何地方工作。爲了「更接近便攜式」,您應該使用無符號整數類型(uint32_t等)。

+0

所以va_list-modification回調是我唯一的選擇獲得用戶特定的值? – Jan 2010-07-09 15:01:30

1

非科學測試。

  • AIX 5.3與4.2 GCC - 工程
  • HP-UX 11.23與ACC 5.56 - 不
  • 的Linux使用GCC 4.1(SUSE 10.2) - 不
  • 的Solaris 10,CC 5.9 - 不是

所有的Linux,Solaris和HP-UX都抱怨args += size;這一行。

否則,很明顯va_arg()被包括在內是有原因的。例如。在SPARC堆棧上的使用完全不同。

+0

好的測試!我很驚訝地發現它甚至在linux gcc下失敗了。那是在64位系統上運行的嗎? – JosephH 2010-07-09 21:29:14

+1

@JosephH:除Linux以外,所有測試都是32位。 IOW,默認編譯模式。實際上,我首先在AIX上進行了測試(因爲我已經打開了一個終端),並認爲它確實有效。僅僅幾分鐘後,就發現這個問題,即使在這個測試中,AIX也是一個異常... – Dummy00001 2010-07-10 00:11:24

0

我可以建議用void指針數組替換變量參數嗎?

+0

好了,但這是另一個有太多開銷的解決方案(不是我已經考慮過了)。我只想將我的參數傳遞給一個函數調用,並不總是聲明一個數組傳遞給每個調用。該功能的使用應儘可能簡單。 – Jan 2010-07-09 15:26:36

0

對於C99,如果我可以假設你所有的參數都是整數類型,但可能只是不同的寬度或符號,你可以用宏來避開。通過這個,你甚至可以將你的參數列表轉換成一個數組,而不會給調用它的用戶帶來痛苦。

#define myFunc(...) myRealFunc(NARGS(__VA_ARGS__), (uintmax_t const[]){ __VA_ARGS__}) 

其中

void myRealFunc(size_t len, uintmax_t const param*); 

NARGS是給你__VA_ARGS__參數的長度的宏。那麼這樣的事情可以是,稱爲,就像一個功能,可以收到va_list

要解釋一下什麼樣的宏觀作用:

  • 它的參數個數地方的myRealFunc
  • 它會創建一個臨時數組(複合文字)的第一個參數的尺寸是否正確以及與參數初始化(全部投給uintmax_t
  • 如果NARGS做得正確,這 只在 預處理時間評估您的參數列表,當t okens和 不在運行時。所以,你的參數 不運行時通過使用評價超過 一次

現在你的回調函數會被myRealFunc叫什麼魔力,你想放置在那裏。由於被調用時他們需要一個不同整數類型的參數,uintmax_t參數param[i]將被轉換回該類型。