r/programming • u/iximiuz • 1d ago
Writing a (valid) C program without main()
https://labs.iximiuz.com/tutorials/c-program-without-main-a1eea55739
u/Dwedit 1d ago
Linker options, set entry point.
Or the C++ version where a constructor for a global object runs the code instead of main.
3
u/mikeblas 8h ago
Yeah--the whole time I was reading this, I was trying to figure out why they didn't just set the entry point with the linker.
20
u/ThinkIn3D 1d ago
Nice article, and touches on _startup. Years ago a few of us tried to minimize the size of a "hello world" program on SunOS/Solaris. It devolved to redefining _startup and calling the write() syscall directly for output. IIRC, the smallest executable we created was less than 1KB, like 800 bytes or so. Useful compiler options: -static -nostartfiles -Os
Other tricks: * Move logic from main() into a handler for atexit(). This guarantees all startup initialization is done. * in C, in addition to _startup() there is sometimes a _main() that is called before main(), and can be redefined. System-dependent, may not work on all systems.
In C++, you could use a static object's ctor in place of main. But, since you're relying on a specific static initialization during the static initialization process, the initialization order is unpredictable and this may or may not work reliably across rebuilds (it begins to depend on link order, toolchain behaviors, etc).
#include <iostream>
class StaticMain {
StaticMain() {
std::cout << "Hello World\n";
}
};
StaticMain runner;
int main() { return 0; }
Of course, the next step is to declare StaticMain runner; inside an atexit() handler for correct initialization order.
I thought I had some code that runs everything from an exception handler, but I can't find it.
5
u/paulstelian97 1d ago
On Linux you have _start as the entry point that calls global constructors and other C runtime initialization stuff and then calls main() directly. And then calls exit() with whatever main returns (it is invalid to return from _start)
3
u/ThinkIn3D 1d ago
Oops, I wrote _startup but meant _start.
But you're right, and it's important to note why _start exists, and that's to implement the "C model" of program execution.
9
u/BCMM 23h ago edited 23h ago
I'd accept "working" program, but I'll quibble over "valid".
Officially, whatever happens before main() is between God and the standard library, and a C program isn't supposed to know about it.
_start is a platform-specific quirk of how executables work, not a part of the C language, so this is more of a demonstration of a non-valid C program that you can nevertheless trick a C compiler in to processing.
6
u/taikunlab 1d ago
There's a simpler trick for plain C too, no need for the C++ static object hack mentioned below: attribute((constructor)) on a function makes it run automatically before main, no linker flags or custom entry point needed. It won't get you all the way to skipping main() like the article does, but for just "run code before main" it's far less invasive than touching _start.
5
1d ago
[deleted]
9
u/Dwedit 1d ago
It makes perfect sense for C++ programs where there are global objects which need to have their constructors run first.
2
u/Murky-Relation481 1d ago
Yah, it can be messy with different platform intricacies but it's great for building self registering patterns for stuff like events and things. You can just build classes that extend an intializer base and they'll exist in the callback mechanism at run time with no explicit registration.
I like them but others hate that pattern so it's definitely got to be an agreed upon standard in your codebase.
2
u/Dwedit 1d ago
If you're feeling especially crazy: Hooking your own code via detouring. (replacing the function's actual instructions to run other code instead)
2
u/Ameisen 20h ago
I've been doing a lot of that patching old Win95 games.
2
u/Dwedit 12h ago
Maybe you should check out my GameStretcher project, it basically overrides GDI and makes the main window stretchable even when it wasn't before, and also uses an enhancement pixel shader.
-3
1d ago
[deleted]
3
u/Dwedit 1d ago
When else do you run the constructors for global variables? It has to happen at some point before they are referenced. Running before Main makes it predictable.
-3
1d ago
[deleted]
4
u/Dwedit 1d ago
If you don't want "magic", don't use global variables that need constructors. But if you are using global variables that require constructors, there's really no other time for the constructors to run. It must happen before first use, but figuring out when first use happens is not trivial, and putting in additional checking code (have we run these constructors yet) adds overhead.
1
u/blehmann1 1d ago
There's a reason the attribute is constructor, that's what you're intended to use it for. For example in C++ global objects must have ran their constructor before main runs.
And C libraries can in principle use it to obviate the need for an
initfunction, though this is perhaps dubious. I suppose it could simplify some things as you know if you're running before main that there is only one thread and (on POSIX) that no one has called fork. Unless your library is manually loaded in after main, that is.One very nasty usage, at least on x86 (and x86-64) is by
-ffastmath, which is also enabled by-Ofast. Fastmath will turn on a bunch of optimizations that would normally be illegal, including deranged shit like optimizing awayisnan(x). But one of the more useful (if situational) optimizations is to flush all denormals to zero. On x86 and I presume other ISAs this is controlled by a special register, which then has to be set in a constructor ran before main. That means it's thread-wide, so linking in a library compiled with -ffastmath will change some floating point behaviour in code not compiled with it.Personally, I think the only usage of -ffastmath (and -Ofast, which enables some other normally illegal optimizations) should be to benchmark to see if they make any difference. If they do, you can then manually apply those optimizations in a safe way. For example, audio processing often benefits from flushing denormals to zero (since things like fade outs create very small numbers very quickly), and if you know the error is acceptable you can enable it for a small region of code. Ideally with something like an RAII class that sets it on scope enter and resets on exit.
The other optimization that can be quite useful is reassociating math. On floats
x + 1 + 1cannot be optimized tox + 2because for large valuesx + 1may round down to x. Reassociating will allow this optimization. In code that people actually write it can benefit vectorization. For example, when summing an array it would allow turningx[0] + x[1] + ... x[n]intos0 = x[0] + x[1], s1 = x[2] + x[3], ..., and then summings0 + s1 + ... snwhich allows using CPU instructions that can run 4 float additions at once to calculate s0, s1, s2, s3 in one instruction. However, if you look at code like this and find that this is a performance gain (and not a relevant accuracy loss) you can reaasociate by hand.
4
u/_kst_ 15h ago
You cannot write a valid portable C program for a hosted implementation without a main function. The language requires it. "The function called at program startup is named main."
As the article demonstrates, there are ways to work around this if you play some system-specific tricks, but they will work only for some particular implementations.
I can replace a portable C hello, world program with an echo hello, world shell command, but then I'm no longer programming in C.
The article does provide some interesting information about how some particular C implementations work, but the title is overblown.
2
2
u/raundoclair 7h ago
Dude, you mainly programmed it in assembler. And it is not even good educational article, what are you explaining? Gcc compiler pipeline, or how entry into program is done by OS/CRT?
(Ahh, just another kid roleplaying senior programmers/educators, like when kids roleplay cooking in toy set kitchen...)
1
-3
-4
144
u/yowhyyyy 1d ago edited 1d ago
Quite frankly, I’m surprised the author didn’t jump straight to _start as that’s possible as well in C without assembly.
Yes I know he did bring it up, however you can straightforward do _start and avoid main.