C,函数转换指针,代码不清楚

AR8*_*R89 2 c pointers casting

来自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; 
}
Run Code Online (Sandbox Code Playgroud)

他在主要功能中所做的演员对我来说非常不清楚.

TesterAlt是一个返回void的函数的指针,它与函数Tester的返回类型相同.他分配给这个函数指针,函数Tester,但是他将后一种返回类型转换为void类型的指针(我不确定这一点).

如果我编译代码改变该行:

void (*TesterAlt)(int, ...) = (void)Tester;
Run Code Online (Sandbox Code Playgroud)

我收到编译器错误:

initializing 'void (*)(int, ...)' with an expression of incompatible type 'void'
void (*TesterAlt)(int, ...) = (void) Tester;
Run Code Online (Sandbox Code Playgroud)

为什么他要做这个演员?他的语法是什么意思?

编辑:我对原始问题不是很清楚,我不明白这种语法以及我必须如何阅读它.

(void *)Tester;
Run Code Online (Sandbox Code Playgroud)

据我所知,Tester被演绎为"指向虚空的指针",但看起来我的解释是错误的.如果它不是指向void的指针,那么你如何阅读该代码?为什么?

glg*_*lgl 6

您收到此错误消息是因为您无法对已转换为的表达式执行任何有用的操作(void).在(void *)原始代码中投指的是指针本身,而不是返回类型.

实际上,(void *)Tester它是从函数指针Tester到void指针的转换.这是一个指向给定地址的指针,但没有有用的信息.

强制转换(void)Tester为"无效类型" - 这会导致表达式无法分配给任何内容.

让我们回到(void *)Tester- 您可以使用此指针将其转换回正确的类型.但在这个意义上什么是"适当的"?那么,"正确"意味着原始函数的函数签名和稍后使用的指针类型必须相同.违反此要求不会导致编译时错误,而是导致执行时的未定义行为.

有人可能会认为签名有一个int然后省略号将覆盖具有固定参数计数的情况,但事实并非如此.确实存在诸如AVR平台之类的系统,它们将void ()(int ign, float x, char y)纯粹地使用寄存器void ()(int, ...)来调用,而将通过将参数推送到堆栈来调用a.

看看这段代码:

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);
}
Run Code Online (Sandbox Code Playgroud)

(请注意,我换floatint...)

在分配b和时vb,我交换各自的功能原型.这样做的结果是,通过引用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__
Run Code Online (Sandbox Code Playgroud)

在这里,我们看到a(),非vararg,通过r20..r25,而va()vararg,通过push堆栈喂养.

关于b()vb(),我故意混淆了定义,忽略了我得到的警告.因此调用如上所述,但由于混淆,它们使用错误的调用约定.这就是UB的原因.在x86上停留时,OP中的代码可能也可能不起作用(可能是这样),但是在切换到x64之后,它可能会开始失败并且没有人第一眼看到它为什么会这样.所以我们再次看到:避免未定义的行为是一个严格的要求.它可能会按预期工作,但您根本没有任何保证.更改编译器标志可能足以更改行为.或者将代码移植到不同的架构中.