4

I have a linked list where each element can contain

typedef struct elem {
    struct elem *next;
    void *data;
} *elem;

Now, say I have a method like...

void blah(int random) {
    printf("testing\n");
}

And I store it inside my list...

elem lst = (elem) malloc(sizeof(elem));
lst->next = NULL;
lst->data = &blah;

How do I now call the method by taking it from my linked list?
Also, am I storing the function in my linked list properly?

Supervisor
  • 582
  • 1
  • 6
  • 20
  • `void *` is not a function pointer – Ed Heal Sep 13 '14 at 01:59
  • But it stores anything, correct? That is - I can store anything inside my linked list - including a function pointer. – Supervisor Sep 13 '14 at 02:01
  • Ah I see. I'm gonna' learn a bit more and post another question later. :/ – Supervisor Sep 13 '14 at 02:03
  • 1
    I may as well point out a subtle bug outside of what the question asked. You may find yourself with strange runtime errors/segfaults if you had this as part of code of a bigger project. The way you defined `elem` as being a `*elem` means that `sizeof(elem)` will return the size of a pointer (dependent on the architecture of the processor/compiler directives - size could be 4 on 32bit and 8 on 64 as an example). – Michael Petch Sep 13 '14 at 03:14

4 Answers4

4

Better:

typedef void (*blah_ptr)(int);
...
typedef struct elem {
    struct elem *next;
    blah_ptr data;
} *elem;
...
lst->data = blah;

A complete example might help:

#include <stdio.h>

typedef void (*blah_ptr)(int);

typedef struct elem {
    struct elem *next;
    blah_ptr data;
} *elem;

void blah (int random) {
  printf ("We're in blah: random=%d...\n", random);
}

void bleep (int nonrandom) {
  printf ("We're in bleep: nonrandom=%d...\n", nonrandom);
}

int
main (int argc, char *argv[]) {

  struct elem lst[2];

  lst[0].next = &lst[1];
  lst[0].data = blah;
  lst[1].next = NULL;
  lst[1].data = bleep;

  lst[0].data(10);
  lst[1].data(20);

  return 0;
}

Here is the output:

We're in blah: random=10...
We're in bleep: nonrandom=20...
FoggyDay
  • 11,962
  • 4
  • 34
  • 48
3

C standard does not allow you to cast a function pointer to void*. In order to store function pointer in a list, you need to define a field in the struct to be of the function pointer type.

One way of doing it is with a typedef:

typedef void (*fptr_int)(int); // This defines a type fptr_int

Now you can use it in your struct like this:

typedef struct elem {
    struct elem *next;
    fptr_int *function;
} *elem;

Assign it the way you did in your example:

lst->function = &blah;

Call it as if it were a function name:

lst->function(123);

The typedef does not need to be specific to the signature of the function that you want to store, though: any function pointer can be cast to another function pointer and back without an error. You can define a "generic" function pointer, like this

typedef void (*void_fptr)();

use it in your struct

typedef struct elem {
    struct elem *next;
    void_fptr *function;
} *elem;

and the use it with appropriate casts:

lst->function = (void_fptr)&blah; // Assign
...
((fptr_int)lst->function)(123);   // Call
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • 1
    Actually you can cast a function pointer to void *. It is very common to do this. You can then dereference the `void *` with something like this `((void (*)(int))(lst->data))(42)`; I have done this without a typedef (although it is recommended for readability) – Michael Petch Sep 13 '14 at 02:10
  • @Supervisor That is what they needed to do for maximal portability. Otherwise, they would have to make assumptions that pointers to data and pointers to functions have the same size, which may not be true on some architectures. For example, Harvard architecture uses different memory spaces for data and for program, and the two spaces could have different size. Without this restriction compilers would have to reserve some space inside each pointer to determine if it's a program space or a data space at runtime. This would be grossly inefficient. – Sergey Kalinichenko Sep 13 '14 at 02:11
  • @MichaelPetch You can do many things that the standard prohibits, and it will often run on your platform, or even on several platforms. The standard prohibits this, though, so the code that you posted in the comment is non-portable. – Sergey Kalinichenko Sep 13 '14 at 02:12
  • @MichaelPetch Look up section 6.3.2.3:8 in the C99 standard for details on what is allowed. Here is [an answer that discusses the reasons behind this decision](http://stackoverflow.com/a/13697654/335858). – Sergey Kalinichenko Sep 13 '14 at 02:15
  • I'll read the standard and not someone's false interpretation. First off, find a C99 or later compiler that will flag that as a warning. First off the standards says "6.3.2.3:8 A pointer to a function of one type may be converted to a pointer to a function of another type and back again; the result shall compare equal to the original pointer". Not any pointer type. So casting to void * and to any other pointer type will yield the same pointer. – Michael Petch Sep 13 '14 at 02:23
  • The second sentence of that in the standard says "If a converted pointer is used to call a function whose type is not compatible with the pointed-to type, the behavior is undefined.". That means that as long as what function you are pointing at is the type you expect then it is valid to call it. If the function pointer calls a function with a different signature (arguments etc) then THAT is undefined. – Michael Petch Sep 13 '14 at 02:24
  • So the standard says that this would be invalid: casting to void * and then calling ((void (*)(int, int))(lst->data))(42,33) . In this example lst->data was pointing to a function blah that takes one parameter but is then cast and called as a function with 2 parameters. – Michael Petch Sep 13 '14 at 02:27
  • @MichaelPetch "First off, find a C99 or later compiler that will flag that as a warning." Here we go: [link](http://ideone.com/Oy3KQc). "ISO C forbids initialization between function pointer and ‘void *’" I hope we've got enough proof at this point, right? – Sergey Kalinichenko Sep 13 '14 at 02:27
  • 1
    The warning occurs only if you specify "-Werror=pedantic". How appropriate ;) – FoggyDay Sep 13 '14 at 02:29
  • Yes, I stand corrected. It seems that on clang it will flag it as a warning and gcc requires pedantic. However rereading the standard I do see my misinterpretation. However he could have defined `void *` to be any function pointer type so this would resolved the incompatibility - change definition of `data` in the struct to be `void (*data)();` and all should be fine. – Michael Petch Sep 13 '14 at 02:51
  • The standard only says that it has to be a pointer to a function. But it can be any type of function one chooses. So `void (*data)();` is simply a generic function pointer that could be cast to any type of function with any number of parameters. As long as the function pointer stored is the same type that you call through it will be happy. – Michael Petch Sep 13 '14 at 02:56
  • 1
    @MichaelPetch That's a valid point, I edited the answer to demonstrate this alternative. Thanks! – Sergey Kalinichenko Sep 13 '14 at 02:58
2

I'm not looking to answer what has already been presented by others like dasblinkenlight but I wanted to make an observation about the way the structure and type was defined and a curious malloc bug that is introduced.

You defined a type *elem. If you were to call sizeof(elem) it will return the sizeof the pointer that points to your data - not the size of the data structure itself. In your case you are allocating too few bytes via malloc and that can lead to memory corruption of the heap. I had made the observation to myself that the code was a bit hard to read as side effect. In particular this line

elem lst = (elem) malloc(sizeof(elem));

My first thought was that elem was a structure not a pointer to a structure. Usually when people create structures they do it this way:

typedef struct _elem {
    struct elem *next;
    void (*data)();
} elem;

Notice elem wasn't defined as a pointer type. Then the malloc would have looked like:

elem *lst = (elem*) malloc(sizeof(elem));

Now it is clear to me that you are dealing with pointers to elements. As well sizeof(elem) will now be the size of the elem data structure and not the size of a pointer that points to the data structure elem

The alternative if you keep your data structure as is would be to do the malloc in this fashion:

elem lst = (elem) malloc(sizeof(*lst));

lst has been defined as elem (which is a pointer). You then dereference the pointer with *lst to tell sizeof you want the size of what you are pointing at (thus size of an elem).

Michael Petch
  • 46,082
  • 8
  • 107
  • 198
1
typedef void (*fptr)(int);
((fptr)lst->data)(42);
John Zwinck
  • 239,568
  • 38
  • 324
  • 436