簡體   English   中英

通過引用傳遞 va_list 的跨平台方式是什么?

[英]What is the cross-platform way to pass a va_list by reference?

我編寫了一個接受va_list的函數,該函數旨在由其調用者迭代調用。 它應該修改va_list並且更改應該保留在調用者中,以便對該函數的下一次調用將繼續下一個參數。

我無法具體發布該代碼,但這里有一個重現這種情況的片段( godbolt 鏈接):

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

void print_integer(va_list ap) {
    printf("%i\n", va_arg(ap, int));
}

void vprint_integers(int count, va_list ap) {
    for (int i = 0; i < count; ++i) {
        print_integer(ap);
    }
}

void print_integers(int count, ...) {
    va_list ap;
    va_start(ap, count);
    vprint_integers(count, ap);
    va_end(ap);
}

int main() {
    print_integers(3, 1, 2, 3);
}

這在我的 x86 平台上有效(打印“1 2 3”),因為va_list是通過“引用”傳遞的(它可能被聲明為一個元素的數組,因此va_list參數衰減為指針)。 但是,它工作對我的ARM平台(打印“111”),其中va_list似乎被定義為指針的東西。 在那個平台上, va_arg總是返回第一個參數。

下一個最好的選擇似乎是讓ap成為一個指針:

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

void print_integer(va_list *ap) {
    printf("%i\n", va_arg(*ap, int));
}

void vprint_integers(int count, va_list ap) {
    for (int i = 0; i < count; ++i) {
        print_integer(&ap);
    }
}

void print_integers(int count, ...) {
    va_list ap;
    va_start(ap, count);
    vprint_integers(count, ap);
    va_end(ap);
}

int main() {
    print_integers(3, 1, 2, 3);
}

這適用於 ARM(使用va_arg(*ap, ...) ),但不能在 x86 上編譯。 當我在 x86 上嘗試print_integer(&ap)時,Clang 說:

錯誤:不兼容的指針類型將“ struct __va_list_tag ** ”傳遞給“ va_list * ”類型的參數(又名“ __builtin_va_list * ”)

這似乎只發生在將va_list的地址作為參數傳遞時,而不是從局部變量中獲取時。 不幸的是,我確實需要我的v變體來獲取va_list對象而不是指向它的指針。

使用va_copy很容易為va_list獲得一致的跨平台值語義。 有沒有一種跨平台的方式來為va_list獲得一致的引用語義?

這里的問題是va_list在 x86 平台上被定義為一個包含 1 個元素的數組(我們稱之為__va_list_tag[1] )。 當作為參數接受時,它會衰減為指針,因此&ap有很大不同,具體取決於ap是函數的參數( __va_list_tag** )還是局部變量( __va_list_tag(*)[1] )。

適用於這種情況的一種解決方案是簡單地創建一個本地va_list ,使用va_copy來填充它,然后將一個指針傳遞給這個本地va_list 天馬行空

void vprint_integers(int count, va_list ap) {
    va_list local;
    va_copy(local, ap);
    for (int i = 0; i < count; ++i) {
        print_integer(&local);
    }
    va_end(local);
}

就我而言, vprint_integersva_copy是必要的,因為該接口vprint_integers接受va_list和不能改變。 對於更靈活的要求,更改vprint_integers以接受va_list指針也很好。

va_list沒有被指定為任何特別的東西,但它被定義為一個對象類型,所以沒有理由相信你不能獲取或傳遞它的地址。 另一個完全繞過是否可以獲取va_list地址問題的非常相似的解決方案是將va_list包裝在一個結構中並傳遞一個指向該結構的指針。

你可能不走運。 7.15(3) 說你正在做的事情具有不確定的效果。

對象ap可以作為參數傳遞給另一個函數; 如果該函數使用參數ap調用va_arg宏,則調用函數中ap的值是不確定的,應在進一步引用ap之前傳遞給va_end宏。

要調用va_argprint_integer造成apprint_integers是不確定的。 在一種架構上它是遞增的,而在另一種架構上則不是。

至於va_list是什么,它可以是任何東西......

...這是一種適合保存宏 va_start、va_arg、va_end 和 va_copy 所需信息的對象類型。

您可能希望考慮采用不同的方法來解決根本問題

第二個程序的問題是如果va_list是數組類型,那么函數參數ap的類型被調整為指針類型。 因此,在每種情況下,該參數都有不同的間接級別。

從 C11 開始,我們可以使用通用選擇器來解決這個問題,以測試ap是否仍然是va_list

void vprint_integers(int count, va_list ap) {
    for (int i = 0; i < count; ++i) {
        print_integer((va_list *)_Generic(&ap, va_list*: &ap, default: ap));
    }

這個解決方案的靈感來自這個答案,它在需要手動配置的兩種情況下使用了一個宏。

關於_Generic注意事項:

  • 我們必須測試&ap ,因為從 C18 開始,數組到指針的衰減是在第一個表達式上執行的。
  • 最初我有_Generic(&ap, va_list*: &ap, default: (va_list *)ap)但是這被編譯器拒絕,編譯器檢查所​​有輸入的所有分支中的約束違規 - 盡管不要繼續檢查約束違規未選定分支的周圍表達式。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM