Function Prefix vs "Function Struct" in C
Asked Answered
V

2

9

I'm trying to figure out best practices in C(11). Because there are no namespaces I can think of two approaches to avoid name collisions:

a.) Function prefixes

void kernel_init(void) { ... }
int kernel_start(void* foo) { ... }

b.) "Function structs"

struct kernel {
    void (*init)(void);
    int (*start)(void* foo);
} kernel;

I'm not asking which approach is prettier because that's rather subjective. What I'm asking is are there any noticeable downsides to either approach apart from code style? This includes little things that are irrelevant at first but become larger problems once the code base grows.

Vitriolic answered 20/9, 2018 at 7:48 Comment(4)
How do you name the functions that you want to assign to the struct members?Larcenous
@Larcenous Good point! Since they are only visible in the source file it wouldn't really matter (but I would go with prefixing)Vitriolic
Another issue with handling functions with structs is that the compiler can no longer inline such functions!Pal
The second approach is commonly used to achieve dynamic dispatch in C (equivalent to virtual methods in C++). But the extra overhead usually discourages its use in any other circumstance.Gemmell
A
9

Interesting, I had never thought of that solution before.

The first one is of course standard, and I'd bet that's what you would find in the vast majority of C projects[*].

The second in theory takes memory, since you're actually declaring a data object full of function pointers. You would of course also need to initialize the kernel variable, i.e. have something like:

...
} kernel = {
  .init = kernel_init,
  .start = kernel_start,
};

But oooh there you go with prefixed functions again. To remove the need for those, the functions have to be static, which I guess is possible if you add extern to the struct declaration in the kernel.h.

So a more complete example could be:

// kernel.h (public header)
typedef struct kernel_api {
  void (*init)(void);
  int (*start)(void* foo);
} kernel_api;

extern const kernel_api kernel;

// in kernel.c
static void init(void)
{
  ...
}

static int start(void *foo)
{
  ..
}

const kernel_api kernel = {
  .init = init,
  .start = start,
};

This might work, but I haven't tried it.

Finally, having explicit data means it takes a sufficiently smart compiler to optimize those out and make more direct calls, but I haven't tried it and it's a bit risky to depend on such. Interesting.

[*] I think I just statistically claimed that I have seen (or thought of) the vast majority of the world's C projects, but that's of course not true. :)

Allusion answered 20/9, 2018 at 7:52 Comment(3)
That's a fair point. In my naivety I just assumed calling the function in the struct and calling a normal function would be the same since they are initialized at compile time, thus their addresses would be known by the compiler just like a normal function callVitriolic
I guess that settles it. Function prefixes are better (less memory consumed and probably faster assuming the compiler can't translate the struct approach to direct function calls) and since it's a completely stylistic decision anyways that's what I will go with!Vitriolic
There's another issue with static functions => your kernel.c file will include files which may not be sanitized and therefore you run the risk of having conflicting declaration of init, start, etc...Kristiekristien
C
3

The function version is what's most commonly used.

The down-side of the struct version is that it's not stand-alone. The function pointers should be set from a "constructor", not from the caller, since that would violate private encapsulation design practice. And what would you name your constructor? You're going to need a prefixed function no matter.

Structs like this are often just used when you wish to achieve polymorphism or when you have callback functions. Some coding styles also use structs to emulate C++ class members, but whether or not that is actually good practice is debatable.

Best OO practice rather calls for a 3rd version, using opaque pointers, meaning you need both functions and structs:

typedef struct kernel_t kernel_t;

kernel_t* kernel_init (void) { ... }
int kernel_start (kernel_t* this, ...) { ... }

where the struct definition is only visible to "kernel.c", not to the caller.

Cyme answered 20/9, 2018 at 9:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.