簡體   English   中英

C、函數指針轉換,代碼不清晰

[英]C, pointer to function cast, unclear code

來自 Mike Ash 的評論: https : //www.mikeash.com/pyblog/friday-qa-2010-01-29-method-replacement-for-fun-and-profit.html#comment-3abf26dd771b7bf2f28d04106993c07b

這是代碼:

void Tester(int ign, float x, char y) 
{ 
    printf("float: %f char: %d\n", x, y); 
} 

int main(int argc, char **argv) 
{ 
    float x = 42; 
    float y = 42; 
    Tester(0, x, y); 

    void (*TesterAlt)(int, ...) = (void *)Tester; 
    TesterAlt(0, x, y); 

    return 0; 
}

他在主要功能中所做的轉換對我來說非常不清楚。

TesterAlt 是指向返回void 的函數的指針,與函數Tester 的返回類型相同。 他分配給這個函數指針,函數 Tester,但他將后者的返回類型轉換為 void 類型的指針(我不確定這一點)。

如果我編譯更改該行的代碼:

void (*TesterAlt)(int, ...) = (void)Tester;

我收到編譯器錯誤:

initializing 'void (*)(int, ...)' with an expression of incompatible type 'void'
void (*TesterAlt)(int, ...) = (void) Tester;

他為什么要做這個選角? 他的語法是什么意思?

編輯:我對我原來的問題不是很清楚,我不明白這個語法以及我必須如何閱讀它。

(void *)Tester;

據我所知,Tester 被強制轉換為“指向 void 的指針”,但看起來我的解釋是錯誤的。 如果它不是指向 void 的指針,那么您如何閱讀該代碼以及為什么?

您收到此錯誤消息是因為您無法對已強制轉換為(void)的表達式執行任何有用的操作。 原始代碼中的(void *) cast 指的是指針本身,而不是返回類型。

實際上, (void *)Tester是從函數指針Tester到 void 指針的轉換。 這是一個只指向給定地址的指針,但沒有關於它的有用信息。

(void)Tester是對“void 類型”的強制轉換 - 這會導致您無法分配給任何內容的表達式。

讓我們回到(void *)Tester - 你可以通過將它轉換回正確的類型來使用這個指針。 但在這個意義上什么是“適當的”? 好吧,“正確”意味着原始函數的函數簽名和后面使用的指針類型必須相同。 違反此要求不會導致編譯時錯誤,但會導致執行時出現未定義的行為。

人們可能認為帶有一個 int 的簽名然后省略號將覆蓋具有固定參數計數的情況,但事實並非如此。 確實有一些系統,例如AVR 平台,它會調用一個void ()(int ign, float x, char y)純粹用寄存器,而void ()(int, ...)將通過推動堆棧的參數。

看看這個代碼:

int va(int, ...);
int a(int, int, char);

int test() {
    int (*b)(int, int, char) = va;
    int (*vb)(int, ...) = a;
    a(1, 2, 3);
    va(1, 2, 3);
    b(1, 2, 3);
    vb(1, 2, 3);
}

(請注意,我將float更改為int ...)

在分配bvb ,我交換了各自的函數原型。 這樣做的結果是,通過引用b ,我確實調用了va ,但編譯器假定了錯誤的函數原型。 這同樣適用於vba

請注意,雖然在 x86 上,這可能有效(我沒有檢查它),我從這段代碼中獲得的 AVR 程序集就像

    # a(1, 2, 3):
    ldi r24,lo8(gs(va))
    ldi r25,hi8(gs(va))
    std Y+2,r25
    std Y+1,r24
    ldi r24,lo8(gs(a))
    ldi r25,hi8(gs(a))
    std Y+4,r25
    std Y+3,r24
    ldi r20,lo8(3)
    ldi r22,lo8(2)
    ldi r23,0
    ldi r24,lo8(1)
    ldi r25,0
    rcall a

    # va(1, 2, 3):
    push __zero_reg__
    ldi r24,lo8(3)
    push r24
    push __zero_reg__
    ldi r24,lo8(2)
    push r24
    push __zero_reg__
    ldi r24,lo8(1)
    push r24
    rcall va
    pop __tmp_reg__
    pop __tmp_reg__
    pop __tmp_reg__
    pop __tmp_reg__
    pop __tmp_reg__
    pop __tmp_reg__

    # b(1, 2, 3):
    ldd r18,Y+1
    ldd r19,Y+2
    ldi r20,lo8(3)
    ldi r22,lo8(2)
    ldi r23,0
    ldi r24,lo8(1)
    ldi r25,0
    mov r30,r18
    mov r31,r19
    icall

    # vb(1, 2, 3)
    push __zero_reg__
    ldi r24,lo8(3)
    push r24
    push __zero_reg__
    ldi r24,lo8(2)
    push r24
    push __zero_reg__
    ldi r24,lo8(1)
    push r24
    ldd r24,Y+3
    ldd r25,Y+4
    mov r30,r24
    mov r31,r25
    icall
    pop __tmp_reg__
    pop __tmp_reg__
    pop __tmp_reg__
    pop __tmp_reg__
    pop __tmp_reg__
    pop __tmp_reg__

在這里,我們看到a()是非可變參數,通過r20..r25獲取,而va() ,可變參數,通過push到堆棧獲取。

關於b()vb() ,我故意混淆了定義,忽略了我收到的警告。 所以調用如上,但由於混淆,它們使用了錯誤的調用約定。 這就是UB的原因。 雖然停留在 x86 上,OP 中的代碼可能會或可能不會工作(可能確實如此),但是在切換到 x64 之后,它可能會開始失敗,並且沒有人第一眼就能看出為什么會這樣。 所以我們再次看到:避免未定義的行為是一個嚴格的要求。 它可能會按預期工作,但您根本無法保證。 更改編譯器標志可能足以改變行為。 或者將代碼移植到不同的架構。

暫無
暫無

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

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