简体   繁体   English

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

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

From this comment of Mike Ash: https://www.mikeash.com/pyblog/friday-qa-2010-01-29-method-replacement-for-fun-and-profit.html#comment-3abf26dd771b7bf2f28d04106993c07b来自 Mike Ash 的评论: https : //www.mikeash.com/pyblog/friday-qa-2010-01-29-method-replacement-for-fun-and-profit.html#comment-3abf26dd771b7bf2f28d04106993c07b

Here is the code:这是代码:

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; 
}

The casting he's doing in the main function is very unclear to me.他在主要功能中所做的转换对我来说非常不清楚。

TesterAlt is a pointer to a function returning void, which is the same return type of the function Tester. TesterAlt 是指向返回void 的函数的指针,与函数Tester 的返回类型相同。 He assign to this function pointer, the function Tester, but he is casting the latter return type to a pointer of type void (I'm not sure of this).他分配给这个函数指针,函数 Tester,但他将后者的返回类型转换为 void 类型的指针(我不确定这一点)。

If I compile the code changing that line:如果我编译更改该行的代码:

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

I get a compiler error:我收到编译器错误:

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

Why he's doing this casting?他为什么要做这个选角? And what his syntax mean?他的语法是什么意思?

Edit: I've not been very clear with my original question, I don't understand this syntax and how I must read it.编辑:我对我原来的问题不是很清楚,我不明白这个语法以及我必须如何阅读它。

(void *)Tester;

From what I know Tester is casted to "a pointer to void", but it looks that my interpretation is wrong.据我所知,Tester 被强制转换为“指向 void 的指针”,但看起来我的解释是错误的。 If it is not a pointer to void then how do you read that code and why?如果它不是指向 void 的指针,那么您如何阅读该代码以及为什么?

You get this error message because you cannot do anything useful with an expression which has been cast to (void) .您收到此错误消息是因为您无法对已强制转换为(void)的表达式执行任何有用的操作。 The (void *) cast in the original code refers to the pointer itself, not to the return type.原始代码中的(void *) cast 指的是指针本身,而不是返回类型。

Indeed the (void *)Tester is a cast from the function pointer Tester to a void pointer.实际上, (void *)Tester是从函数指针Tester到 void 指针的转换。 This is a pointer which just points to the given address, but has no useful information on it.这是一个只指向给定地址的指针,但没有关于它的有用信息。

A cast to (void)Tester is a cast to a "void type" - which results in an expression which you just cannot assign to anything.(void)Tester是对“void 类型”的强制转换 - 这会导致您无法分配给任何内容的表达式。

Let's return to (void *)Tester - you can use this pointer by casting it back to the proper type.让我们回到(void *)Tester - 你可以通过将它转换回正确的类型来使用这个指针。 But what is "proper" in this sense?但在这个意义上什么是“适当的”? Well, "proper" means that the function signatures of the original function and the pointer type used later must be identical.好吧,“正确”意味着原始函数的函数签名和后面使用的指针类型必须相同。 Violating this requirement does not lead to a compile time error, but to undefined behaviour on execution time.违反此要求不会导致编译时错误,但会导致执行时出现未定义的行为。

One might think that a signature with has one int and then the ellipsis would cover a case with a fixed argument count, but that is not the case.人们可能认为带有一个 int 的签名然后省略号将覆盖具有固定参数计数的情况,但事实并非如此。 There are indeed systems out there such as the AVR platform which would call a void ()(int ign, float x, char y) purely with registers, while a void ()(int, ...) would be called by pushing the arguments to the stack.确实有一些系统,例如AVR 平台,它会调用一个void ()(int ign, float x, char y)纯粹用寄存器,而void ()(int, ...)将通过推动堆栈的参数。

Have a look at this code:看看这个代码:

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);
}

(note that I changed float to int ...) (请注意,我将float更改为int ...)

On assigning b and vb , I swap the respective function prototypes.在分配bvb ,我交换了各自的函数原型。 The result of this is that by referring to b , I indeed call va , but the compiler assumes a wrong function prototype.这样做的结果是,通过引用b ,我确实调用了va ,但编译器假定了错误的函数原型。 The same holds for vb and a .这同样适用于vba

Note that while on x86, this might work (I didn't check it), the AVR assembly I get from this code is like请注意,虽然在 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__

Here we see that a() , being non-vararg, gets fed via r20..r25 , while va() , being vararg, gets fed via push ing to the stack.在这里,我们看到a()是非可变参数,通过r20..r25获取,而va() ,可变参数,通过push到堆栈获取。

Concerning b() and vb() , I deliberately mixed up the definitions, ignoring the warnings I got about that.关于b()vb() ,我故意混淆了定义,忽略了我收到的警告。 So the calls are as above, but they use the wrong calling conventions due to the mix-up.所以调用如上,但由于混淆,它们使用了错误的调用约定。 This is the reason of this being UB.这就是UB的原因。 While staying on x86, the code in the OP may or may not work (probably it does), but already after a switch to x64, it may start to fail and no one sees at first glance why it does.虽然停留在 x86 上,OP 中的代码可能会或可能不会工作(可能确实如此),但是在切换到 x64 之后,它可能会开始失败,并且没有人第一眼就能看出为什么会这样。 So we see once again: avoiding undefined behaviour is a strict requirement.所以我们再次看到:避免未定义的行为是一个严格的要求。 It may work as expected, but you have no guarantees at all.它可能会按预期工作,但您根本无法保证。 Changing compiler flags may be sufficient to change the behaviour.更改编译器标志可能足以改变行为。 Or porting the code to a different architecture.或者将代码移植到不同的架构。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM