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
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. 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).
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. If it is not a pointer to void then how do you read that code and why?
You get this error message because you cannot do anything useful with an expression which has been cast to (void)
. The (void *)
cast in the original code refers to the pointer itself, not to the return type.
Indeed the (void *)Tester
is a cast from the function pointer Tester
to a void pointer. 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.
Let's return to (void *)Tester
- you can use this pointer by casting it back to the proper type. 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. 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.
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
...)
On assigning b
and vb
, I swap the respective function prototypes. The result of this is that by referring to b
, I indeed call va
, but the compiler assumes a wrong function prototype. The same holds for vb
and a
.
Note that while on x86, this might work (I didn't check it), the AVR assembly I get from this code is like
# 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.
Concerning b()
and vb()
, I deliberately mixed up the definitions, ignoring the warnings I got about that. 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. 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. 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.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.