|
Help with C function pointers |
kenmasters1976
Member #8,794
July 2007
|
I have some code in my project that uses C function pointers and it's being built with gcc on Linux. Consider the following sample code; I'm trying to understand why the first call to my_function() won't work but the second does: 1#include <stdio.h>
2
3float dummy_function(float f) {
4 printf("Argument value:\t%.10lf\n", f);
5 return f * 2;
6}
7
8float dummy_function2(float *f) {
9 printf("Argument value:\t%.10lf\n", *f);
10 return *f * 2;
11}
12
13int main() {
14 float f = 3.1416;
15 float (*my_function)() = dummy_function; 16 my_function(f); 17 my_function = dummy_function2; 18 my_function(&f); 19}
The output for this code is the following: Argument value: 0.0000000000 Argument value: 3.1415998936 I'm aware that using the correct function signature as in: int main() { float f = 3.1416; float (*my_function)(float) = dummy_function; my_function(f); float (*my_function2)(float *) = dummy_function2; my_function2(&f); } will make the code work as expected but I'm curious as to why the first form doesn't work. I thought declaring a function with an empty argument list meant you could call that function with an arbitrary number of arguments of any type. Thanks.
|
Chris Katko
Member #1,881
January 2002
|
kenmasters1976 said: I thought declaring a function with an empty argument list meant you could call that function with an arbitrary number of arguments of any type. Don't you mean a variac function? [edit] So the key problem you're saying, is you can't reassign the function pointer to a function with a different type signature, and then use it using the correct-for-that-new-function arguments? Recasting a function pointer is pretty evil and undefined behavior. https://stackoverflow.com/questions/559581/casting-a-function-pointer-to-another-type I would "assume" that it's because the function call itself involves setup assembly that has to match the receiving function. Or it's just so evil that language developers thought nobody would ever do it so they didn't codify the results. So basically, the reason you get 0.000 and not an immediate segfault is because you've been lucky and it's probably reading some uninitialized extra data area still within the current page. The solution people show for c is using a union with multiple function pointers. typedef union { double (*func_one)(double x, double a, double b, double c); double (*func_two)(double x, double p[], double c); } func_one_two; Then again, you can be super evil and probably get yours to work by recasting the function pointer ala this disgusting code: ala C = ((myFunc2PtrType)(*(myFuncPtrA[1])))(A[0],A[1]); Both of those are from the linked posts. [edit] "Supposedly" it's "against standards, but works in the wild" according to one post that says Glib uses the pattern significantly for its slots-and-signals implementation. I was going to ask how is the compiler even letting you do your current method (without an explicit cast) because it's type violation. But apparently the compiler was warning you. main.c:15:30: warning: initialization of float (*)() from incompatible pointer type float (*)(float) [-Wincompatible-pointer-types] 15 |
-----sig: |
Ariesnl
Member #2,902
November 2002
|
kenmasters1976 said: I thought declaring a function with an empty argument list meant you could call that function with an arbitrary number of arguments of any type.
that would be elipsis (...) like printf uses :-) Perhaps one day we will find that the human factor is more complicated than space and time (Jean luc Picard) |
Peter Hull
Member #1,136
March 2001
|
kenmasters1976 said: I thought declaring a function with an empty argument list meant you could call that function with an arbitrary number of arguments of any type. It just means "ignore checking here" - as CK said, it triggers a warning on newer compilers, and, just because it compiles, doesn't mean it will run correctly (this is C after all!). Also, the C standard says it's "obsolescent". See https://stackoverflow.com/questions/20835534/function-pointer-without-arguments-types
|
torhu
Member #2,727
September 2002
|
Floats are not passed on the regular stack, maybe that could explain why one works and not the other? You can make this work if you tell the compiler what the real prototype is when calling the functions, either by casting or by using a function pointer of the correct type. But you probably shouldn't be doing this anyway, unless you have some special reason. If you want arguments of unknown types you can use void pointers or variadic functions. But you have to keep track of the actual types yourself, and not mess up. It's a risky way to program. |
kenmasters1976
Member #8,794
July 2007
|
Thank you all for your replies, makes a lot of sense now. The code does indeed generate a warning but I've been using it for quite some time just "because C allows it". The way I was using it was, for example, to use a int (*my_function)() variable to store a function pointer to any function that returns an int and then at some later point in the code I'd call the function as in the first post. I was using this with pointers and never had an issue so I assumed it was safe to call the function this way but now I'm going to start using the actual function signature; the union solution will come in handy. Thanks.
|
Chris Katko
Member #1,881
January 2002
|
torhu said: It's a risky way to program. I was just thinking about that. Any time you go out of your way to remove static typing in a static language, you're in dangerous territory! If you're doing that--and not doing it because some obscure commercial product requires you to do so for your day job--I'd say, instead of hacking around a language, pick a more expressive language.* C++/D/C#/what-have-you. *C is simple and that's what it's great at! I'm no C guru, but it at least seems that anytime you're using void pointers or recasting function pointers, you're breaking one of the best tools for ensuring your program is valid, the type checker. You can pass it literally anything (including junk data) and it's going to call it. Super interesting post though! I never really thought about recasting function pointers before. -----sig: |
RmBeer2
Member #16,660
April 2017
|
This's interesting, I did not know that in C you can freely assign the parameters even if in the declared pointer function you do not have any. This cannot be done in C++. I have checked from gdb and I have come to the conclusion that it is a error from compiler, it would be necessary to see if other compilers work the same or work accordingly. According to the code, in case incorporating parameters out of nowhere is valid, it should work without problems. The function code for dummy_function for either a function pointer or a normal function is almost the same only with a slight mismatch. For this test i add 'dummy_function(f);' in the line 19. 1my_function(f);
2 <+58>: flds -0x14(%ebp)
3 <+61>: sub $0x8,%esp
4 <+64>: lea -0x8(%esp),%esp
5 <+68>: fstpl (%esp)
6 <+71>: mov -0x10(%ebp),%eax
7 <+74>: call *%eax
8 <+76>: fstp %st(0)
9 <+78>: add $0x10,%esp
10
11dummy_function(f);
12 <+107>: flds -0x14(%ebp)
13 <+110>: sub $0xc,%esp
14 <+113>: lea -0x4(%esp),%esp
15 <+117>: fstps (%esp)
16 <+120>: call 0x11ad <dummy_function>
17 <+125>: fstp %st(0)
18 <+127>: add $0x10,%esp
19 <+130>: mov $0x0,%eax
in 61~64 and 110~113. and now in begin of dummy_function: <+0>: push %ebp <+1>: mov %esp,%ebp <+3>: push %ebx <+4>: sub $0x4,%esp // -3 <+7>: call 0x12c5 <__x86.get_pc_thunk.ax> <+12>: add $0x2e47,%eax and from printf of dummy_function, that load a float and store a double for printf: <+17>: flds 0x8(%ebp) // +2 <+20>: sub $0x4,%esp <+23>: lea -0x8(%esp),%esp // -1 (-2) <+27>: fstpl (%esp) <+30>: lea -0x1ff8(%eax),%edx <+36>: push %edx <+37>: mov %eax,%ebx <+39>: call 0x1040 <printf@plt> <+44>: add $0x10,%esp
0x8(%ebp) fit from a float in the 4x4 bytes added before enter in the function (___f), but not for double added before enter in the function (__ff). EDIT: πππ π BlackRook WebSite (Only valid from my installer) π C/C++ π GNU/Linux π IceCream/Cornet π πππ Rm Beer for Emperor 2021! Rm Beer for Ruinous Slave Drained 2022! Rm Beer for Traveler From The Future Warning Not To Enter In 2023! Rm Beer are building a travel machine for Go Back from 2023! Rm Beer in an apocalyptic world burning hordes of Zombies in 2024! |
Kitty Cat
Member #2,815
October 2002
|
RmBeer2 said: This's interesting, I did not know that in C you can freely assign the parameters even if in the declared pointer function you do not have any. This cannot be done in C++.
Technically you can, you just need to be explicit. -- |
|