5 ms·
The author tried using __COUNTER__ and a static array of function pointers, and narrowly missed a workable idea—a static linked list! Forward declarations pun
by mrgriffin 9y ago
The author tried using __COUNTER__ and a static array of function pointers, and narrowly missed a workable idea—a static linked list! Forward declarations pun definitions, and can be used roughly as follows:
struct Element {
int value;
struct Element *next;
}
#define ELEMENT(value) _ELEMENT(value, __COUNTER__)
#define _ELEMENT(value, n) \
struct Element CAT(e, SUCC(n)); \
struct Element CAT(e, n) = { (value), &CAT(e, SUCC(n)) }
You can then walk from e0 through the linked list, but because of how the initialization works, you're done when the current element has `next == 0`.
int main(void)
{
struct Element *e = &e0;
while (e->next) {
printf("%d\n", e->value);
e = e->next;
}
}
Here's a repl.it: https://repl.it/repls/DetailedYoungAsianconstablebutterfly https://repl.it/repls/DetailedYoungAsianconstablebutterfly
- mort96 9y agoThat's really neat, I hadn't considered that. I will definitely try it out, and might end up using it for the library.
- mrgriffin 9y agoYeah, it's a weird one! I discovered the idea while building my own toy unit test library, but I never got around to finishing it. The biggest downside is that (as far as I know) it's impossible to get this approach to coordinate across translation units, so you're forced to define a main in each test file (via the library's header file?). But I doubt that matters too much in practice.
- nialv7 9y agoThere's a simple solution you're missing here: __attribute__((constructor))
- kbob 9y agoThis is what I came to say. Here is a working example that builds an array of function pointers at program initialization time. https://github.com/kbob/schetoo/blob/master/prim.h#L215 https://github.com/kbob/schetoo/blob/master/prim.h#L215 This also works across multiple source files, unlike __COUNTER__.
- vpalanc1 9y agoI think the other solutions proposed are better, but: > Sadly, you can't set the value of an index in an array like that in the top level in C, only inside functions. This is not exactly true. An assignment is a value, you can do e.g. int a = b[x] = 0; (which implies that you can do your array-index assignments at the global level by creating nonsensical/unused variables and initializing them).
- deleted 9y ago[deleted]
- sounds 9y agoThe Linux Kernel also used another trick - placing data in a special named "section." The linker's job is to combine all the separate pieces in a "section" (special linker term) and write out one section. Using a special named section lets you create an array using macros, though using it for function pointers is probably safe and using it for other data types might be pretty fragile. http://www.compsoc.man.ac.uk/~moz/kernelnewbies/documents/initcall/kernel.html http://www.compsoc.man.ac.uk/~moz/kernelnewbies/documents/in...
- AceJohnny2 9y agoI was impressed when I discovered our codebase was using this trick to implement new CLI commands. Each C file would implement the functions and helptext etc for implementing a command, but I couldn't find where each command was being registered in the central parser. Because they had to be registered in some central list, right?... Turned out the macros I overlooked at the end of each file placed the struct defining the command in a special linker section, and the parser init would go and load all the commands from that section. Interesting to know that this trick already existed in the Linux kernel. I'm (perhaps perversely) disappointed we aren't using more of the linker's power in many C codebases.
- BeeOnRope 9y agoYeah, the linker offers a lot of magic waiting to be discovered. A primary problem with linker magic is that it is usually very non-portable. Hacks you do with macros may be ugly but at least they work everywhere, unless you used a non-standard behavior. Linker tricks tend to be platform specific, and even more so that other tricks (eg POSIX platforms share a lot of common behavior, but still tend to have quite different linkers).
- openasocket 9y agoI had a minor epiphany a few months ago where I realized the linker can be seen as sort of doing dependency injection for a bunch of object files. In theory, you could have a program that depends on some set of symbols, two object files that each include the symbols, and a linker script that at load time checks some configuration parameter before determining which subsystem to link with. You could even get really fancy and dynamically generate another object file with those symbols that acts as a proxy. And from there you could basically implement Spring for C, which obviously is what everyone wants to do ;)
- cperciva 9y agoNote that this does not work if you make use of __COUNTER__ anywhere else in the file (directly or indirectly).
- mrgriffin 9y agoThat's true, and in the event that you're actually using __COUNTER__ for something you could build something similar out of #include with a header that increments an internal number. // COUNTER.h #if !defined(COUNTER) #define COUNTER 0 #elif COUNTER == 0 #undef COUNTER #define COUNTER 1 #elif COUNTER == 1 #undef COUNTER #define COUNTER 2 ... #end if And then change each invocation of ELEMENT to have a leading #include "COUNTER". #include "COUNTER.h" ELEMENT(x) #include "COUNTER.h" ELEMENT(y) Note that if you take this approach then (as far as I know) you're actually writing standard C!