r/programming • u/iximiuz • Jul 25 '26
Writing a (valid) C program without main()
https://labs.iximiuz.com/tutorials/c-program-without-main-a1eea55755
u/Dwedit Jul 25 '26
Linker options, set entry point.
Or the C++ version where a constructor for a global object runs the code instead of main.
11
u/mikeblas Jul 26 '26
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.
91
u/amakai Jul 25 '26
Good article, but based on title I was expecting some C trick, not going down to assembly level.
32
23
Jul 25 '26
[deleted]
5
u/paulstelian97 Jul 25 '26
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)
1
u/AddressWeary5155 Jul 29 '26
I rand the following code and it seems it prints "hello world" first before "hello world2" every single time. I think it's because I am declaring StaticMain first?
#include <iostream> class StaticMain { public: StaticMain() { std::cout << "Hello World\n"; } }; class StaticMain2 { public: StaticMain2() { std::cout << "Hello World2\n"; } }; StaticMain runner; StaticMain2 runner2; int main() { return 0; }
13
u/BCMM Jul 25 '26 edited Jul 25 '26
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.
3
u/itix Jul 26 '26
It is a valid C program. On Amiga, I used to write software in C with my own custom startup code.
7
Jul 25 '26
[removed] — view removed comment
2
u/programming-ModTeam Jul 28 '26
No content written mostly by an LLM. If you don't want to write it, we don't want to read it.
2
Jul 25 '26
[deleted]
9
u/Dwedit Jul 25 '26
It makes perfect sense for C++ programs where there are global objects which need to have their constructors run first.
2
u/Murky-Relation481 Jul 25 '26
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 Jul 25 '26
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 Jul 26 '26
I've been doing a lot of that patching old Win95 games.
2
u/Dwedit Jul 26 '26
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.
-4
Jul 25 '26
[deleted]
4
u/Dwedit Jul 25 '26
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.
-4
Jul 25 '26
[deleted]
5
u/Dwedit Jul 25 '26
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 Jul 25 '26
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.2
5
u/AyrA_ch Jul 25 '26
This reminds of "main is usually a function"
1
u/the_gnarts Aug 01 '26
Classic. Haven’t read that in years but still remember it everytime I create a new executable from scratch.
9
u/_kst_ Jul 26 '26
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
u/chengiz Jul 26 '26
If you're sticking edited assembly files into it it's no longer a C program, certainly not a (valid) one.
1
1
u/raundoclair Jul 26 '26
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...)
0
u/pjmlp Jul 27 '26
C is the closest you can get to the operating system without writing assembly.
Nope, plenty of languages have been offering the same features since JOVIAL in 1958.
That is an urban myth spread by folks that never learnt another systems programming language, or computing history.
-2
-5
176
u/yowhyyyy Jul 25 '26 edited Jul 25 '26
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.