1

I have a struct which looks somewhat like this:

struct Data
{
  int a;
  float b;
  char *c;

  int (*read)(struct Data *data, int arg1);
  int (*write)(struct Data *data, int arg1, int arg2);
  int (*update)(struct Data *data, int arg1, int arg2, int arg3);
  int (*erase)(struct Data *data, int arg1);
  /* ... */
}

The ... means that there is bunch of other function pointers smiliar to above (that is, they all return an int and take pointer to Data as first argument, but other arguments may differ).

Let's say there are 20 function pointers total. In a special function DataInit(), I assign functions to them, like this:

Data->read = readA;
Data->write = writeA;
/* readA() and writeA() are functions defined elsewhere in the code, with argument lists same as corresponding function pointers */

Now I have to do the same for another object of type Data, which differs in a way that it's "read-only"; it basically means that from those 20 function pointers 15 has to be assigned such that after invoking them they should return error code NOT_SUPPORTED. The rest stay the same (for example, readA() is assigned to function pointer (*read) like above).

I was wondering if there's a way to do it without implementing a function for each pointer (for example, updateB() that takes three arguments and its body is just return NOT_SUPPORTED). Unfortunately, I cannot just set them to NULL.

I was thinking about preprocessor macros but it's black magic to me, honestly.

BlackM
  • 3,927
  • 8
  • 39
  • 69
Bart90
  • 11
  • 2
  • "Unfortunately, I cannot just set them to NULL." - you can, you just have to check the pointer before invoking the function. – Karoly Horvath Jun 22 '15 at 10:56
  • Do you have any control of the rest of the code? Because this looks like a very good case where some pointers should allowed to be `NULL`, and my suggestion is that you fix that issue instead. – Some programmer dude Jun 22 '15 at 10:56
  • @JoachimPileborg Unfortunately, I don't. Specification says, that for 'read-only' object, I have to return 'not supported' explicitly. – Bart90 Jun 22 '15 at 11:38
  • Then it seems like you have no other choice than to write a set of functions that return `NOT_SUPPORTED`. However, you only need to write then once, and then you can reuse them for when needed. You could have e.g. `unsupported_1` for all functions taking one extra argument, `unsupported_2` for all taking two extra arguments, etc. – Some programmer dude Jun 22 '15 at 11:43

4 Answers4

2

No, you may not cast a function pointer to a function pointer of different type (or even worse, to a different pointer type). This causes undefined behavior in the C standard for a good reason.

There are currently architectures out there where this isn't just a theoretical problem that everyone gets away with, but it can actually crash your program in unexpected ways. Read this blog post if you want details.

Art
  • 19,807
  • 1
  • 34
  • 60
  • That's an interesting blog post, but passing too many arguments to a function like I suggest in my answer should still work on ia64. – nwellnhof Jun 22 '15 at 22:29
  • @nwellnhof Yes, I've played games with function pointers, actually exactly in a situation like this, but then I knew exactly the target platform the code would be running on and what I could get away with. It's quite likely that the code of the question will work just fine. But without knowing the platform in this case the answer cannot be anything other than "undefined, don't do it". The anecdote was just to show that the undefinedness of the behavior wasn't just to be pedantic for the sake of worshipping the standard. – Art Jun 23 '15 at 07:06
0

I don't know whether my suggestion is legal or not, but I want to suggest this:

int data_not_supported_(struct Data *thiz, ...)
{
    return NOT_SUPPORTED;
}

And there might be no problem if your compiler uses cdecl calling convention, where the number of argument doesn't affect on the caller.

ikh
  • 10,119
  • 1
  • 31
  • 70
0

Yes, you can use a single function

int unsupported() {
    return NOT_SUPPORTED;
}

and cast to correct the function pointer type when initializing your struct:

Data->write = (int (*)(struct Data *, int, int))unsupported;

These casts are ugly, so it's more readable to have a typedef for each function:

typedef int
(*write_t)(struct Data *, int, int);

And then:

Data->write = (write_t)unsupported;
nwellnhof
  • 32,319
  • 7
  • 89
  • 113
  • Invoking those functions may lead to undefined behavior, however. – Lundin Jun 22 '15 at 11:32
  • @Lundin That's right, but I'd be surprised to see a platform where this doesn't work. I have used similar hacks successfully on Linux, Darwin, Windows, FreeBSD, OpenBSD, and Solaris. – nwellnhof Jun 22 '15 at 22:24
  • I have done so as well (for example as a trick to [smoothly load functions from a Windows dll](http://stackoverflow.com/questions/8696653/dynamically-load-a-function-from-a-dll/23763255#23763255)), usually the size of a function pointer is always the same no matter calling convention. The problem arises when the function should be called, then you have to ensure that the calling convention is correct. Also, I suppose aliasing might be an issue, as the compiler is free to assume that a function pointer never points to a function of a different kind. – Lundin Jun 23 '15 at 06:22
0

As mentioned, function pointer casts will most likely result in undefined behavior on most systems.

A feasible solution to the problem is this:

typedef int func_t (struct Data* this, void* arg);

struct Data
{
  int   a;
  float b;
  char* c;

  func_t* read;
  func_t* write;
  ...
};


// later on in the code:
int update_function (struct Data* this, void* arg)
{
  struct my_type* m = (struct my_type*)arg;

  // use m
}
Lundin
  • 195,001
  • 40
  • 254
  • 396