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/in...
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.
This is the reason why I do not like such tricks. This obviously hurts maintainability and probably portability. Is the tradeoff really worth it? Plain stupid registration is just a single line. KISS.
"Everyone knows that debugging is twice as hard as writing a program in the first place. So if you're as clever as you can be when you write it, how will you ever debug it?" –Brian Kernighan
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).
I overrided malloc() and free() with my own memory tracking functions.
On Windows side it wasn't needed, as VS already provided some APIs for it.
https://msdn.microsoft.com/en-us/library/bb918180.aspx
Another interesting thing is that it relies on the linker ordering names in alphabetical order.
The initialization order is implementation dependent in module/package initialization blocks.
The only guarantee is that the modules being imported run first than the one being executed.
Of course it still won't be deterministic if you load classes in parallel in multiple threads.
For example
class A { static final int x = B.y; static final int y = 1; }
class B { static final int x = A.y; static final int y = 2; }
will not produce any static initializers and will do what you probably wanted because everything can be turned into a class constant and resolved at compile time. However this class A { static final Integer x = B.y; static final Integer y = 1; }
class B { static final Integer x = A.y; static final Integer y = 2; }
will not work the way you might want because there is no order in which the static initializers can be run that will do everything in the correct order. // 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!> 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).
__attribute__((constructor))
https://github.com/kbob/schetoo/blob/master/prim.h#L215
This also works across multiple source files, unlike __COUNTER__.
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.