I 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.
> I couldn't find where each command was being registered in the central parser.
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
Yeah, 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).
I 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 ;)
That's also how C++ static constructor/destructor calls work --- a section contains entirely function pointers, which the init/uninit code in the RTL calls before and after main().
Yeah, the Macintosh Programmer's Workshop (MPW) did pretty much the same thing (back in 1987 or so). I forget the ordering guarantee, it might have been lexicographic by filename. Global initialization in C++ has been a horrible mess, portability-wise, for literally decades.
It's true the JVM specifies this, but it doesn't mean it's always easy or nice to figure out what's going on.
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.
I wish so much that linker arrays were part of the c and c++ standards! They are much more flexible than pre-main static constructors, and could be used to implement them at the user level in a less magical way!
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...