Allegro.cc - Online Community

Allegro.cc Forums » Programming Questions » Help with C function pointers

This thread is locked; no one can reply to it. rss feed Print
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:

#SelectExpand
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
avatar

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

https://stackoverflow.com/questions/16770690/function-pointer-to-different-functions-with-different-arguments-in-c

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:
β€œPrograms should be written for people to read, and only incidentally for machines to execute.” - Structure and Interpretation of Computer Programs
"Political Correctness is fascism disguised as manners" --George Carlin

Ariesnl
Member #2,902
November 2002
avatar

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 :-)
You'll need argumentlist macro's to use it properly va_list, va_start, va_arg and va_end.

Perhaps one day we will find that the human factor is more complicated than space and time (Jean luc Picard)
Current project: [Star Trek Project ] Join if you want ;-)

Peter Hull
Member #1,136
March 2001

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
avatar

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
avatar

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:
β€œPrograms should be written for people to read, and only incidentally for machines to execute.” - Structure and Interpretation of Computer Programs
"Political Correctness is fascism disguised as manners" --George Carlin

RmBeer2
Member #16,660
April 2017
avatar

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.

#SelectExpand
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.
There are really no clear instructions even with bytecodes, I must assume that 'flds' adds the value on stack and 'fstpl/s' retrieves a value from the stack, i judging from how it has been spread in the code, I must assume that 's' occupies 4 bytes (float) and 'l' occupies 8 bytes (double).
then dummy_function(110~113) work fine because always work with float, instead of my_function(61~64) that load float and store double.

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).
In the function only load a float and not double, that's where cause problems and destroy the values. The memory area is always valid, but the loaded data is wrong.

EDIT:
It's very likely that the compiler has no way of knowing what type of variable to work with, even if 'float' has been declared, it's disconnected between the function and the input to the function.

🌈🌈🌈 🌟 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
avatar

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.
float (*my_function)(...)
is valid in C/C++, and is equivalent to
float (*my_function)()
in C. It's a variadic function, taking a series of parameters on the stack that the callee needs to pop off to use, using the varags API. The way parameters are passed over varargs can be different to how they're passed with explicit type parameters, which is why using it as a generic signature to other non-variadic functions doesn't work.

--
"Do not meddle in the affairs of cats, for they are subtle and will pee on your computer." -- Bruce Graham

Go to: