[英]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
...)
在分配b
和vb
,我交換了各自的函數原型。 這樣做的結果是,通過引用b
,我確實調用了va
,但編譯器假定了錯誤的函數原型。 這同樣適用於vb
和a
。
請注意,雖然在 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.