Giao diện
TeguNews
Công nghệ

Writing a (valid) C program without main()

Article URL: https://labs.iximiuz.com/tutorials/c-program-without-main-a1eea557 Comments URL: https://news.ycombinator.com/item?id=49048901 Points: 8 # Comments: 0

Hacker News4 phút đọc

Table of contents Reset Progress Are you sure you want to reset your progress for this tutorial? This action cannot be undone. Cancel This tutorial was created by a community author.

Community content is reviewed by the iximiuz Labs team on a best effort basis. Tutorial on Programming, Linux Last updated: Jul 25, 2026Writing a (valid) C program without main()by Başar SubaşıWalk through the C compilation pipeline: preprocessor, compiler, assembler, and linker. Start with a normal hello world, inspect macros and generated assembly, and end by producing a running binary that has no main() function.

Why C?Most programming languages start execution from a main() function. Go has func main(), Java has public static void main(), and C has int main().

So why did I pick C for this tutorial?C is the closest you can get to the operating system without writing assembly. The Linux kernel itself is written in C, and the system call interface that every program relies on is designed with C conventions in mind.

When you learn how C programs are compiled, linked, and executed, you are learning how the Linux API actually works.A typical C program starts with a main() function. But where does main() come from?

The compiler? The linker? The operating system?

In this tutorial, you will walk through the entire C compilation pipeline. You will start with a normal hello world, watch the preprocessor expand a macro, watch the compiler turn C into assembly, and watch the assembler and linker turn assembly into a binary. Then you will remove main() from the picture and still produce a working executable.

The playground is a vanilla Ubuntu 24.04 machine with gcc, as, ld, and friends already installed.Step 1: Hello WorldCreate a file named hello.

c with the following content:#include <stdio.h> int main(void) { printf("Hello, world!\n"); return 0; } Copy to clipboardCompile it and run it:gcc hello.

c -o hello ./hello Copy to clipboardYou should see the familiar greeting. Now ask yourself: how many separate tools ran when you typed that single gcc command?

Behind the scenes, gcc is a driver that runs several tools in sequence:Preprocessor (cpp) - handles #include, #define, and conditional compilation.Compiler (cc1) - turns C source into assembly.Assembler (as) - turns assembly into an object file.

Linker (collect2 / ld) - links object files and libraries into the final executable.The rest of the tutorial visits each of these stages.The four stages of the C compilation pipeline.

Step 2: The preprocessorThe preprocessor is a text processor. It does not understand C types or control flow; it only expands directives and macros. The -E flag tells gcc to stop after preprocessing.

Add a macro to hello.c so it looks like this:#include <stdio.h> #define GREETING "Hello, preprocessed world!

\n" int main(void) { printf("%s", GREETING); return 0; } Copy to clipboardRun the preprocessor and read the expanded output:gcc -E hello.c -o hello.i Copy to clipboardOpen hello.

i and try to find the line where GREETING used to be. What happened to it? And why is the file so much larger than the original source?

Step 3: From C to assemblyThe compiler turns C into assembly. The -S flag tells gcc to stop after that stage.gcc -S hello.

c -o hello.s Copy to clipboardOpen hello.s and look at the function labeled main.

The exact instructions depend on the architecture, but on x86_64 you will see something like a function prologue, a call to printf, and a return.To see how optimization changes the output, create a file named loop.c with this function:int sum(int n) { int s = 0; for (int i = 0; i < n; i++) { s += i; } return s; } Copy to clipboardGenerate three versions of the assembly:gcc -O0 -S loop.

c -o loop-O0.s gcc -O2 -S loop.c -o loop-O2.

s gcc -Os -S loop.c -o loop-Os.s Copy to clipboardCompare the files.

With -O0, the compiler is literal: it keeps the loop, the counter, and the addition exactly as written. With -O2, the optimizer may unroll the loop, vectorize it, or even replace it with the closed-form formula n * (n - 1) / 2. With -Os, the optimizer tries to keep the code small while still being fast.

Optimization is not magic. It is the compiler applying a set of transformations to the intermediate representation of your program. The more information the compiler has, the more aggressively it can optimize.

Step 4: From assembly to object, from object to binaryThe assembler turns the .s file into an object file, and the linker turns object files into an executable. You can drive these steps manually.

Assemble the hello-world assembly:gcc -c hello.c -o hello.o Copy to clipboardThe -c flag tells gcc to stop after assembling.

Inspect the object file:nm hello.o objdump -d hello.o Copy to clipboardYou will see the symbols main, printf, and perhaps references to the .

rodata section. printf is an unresolved symbol because the object file does not contain the implementation of printf. The linker will resolve it from the C library.

Link the object file into a binary:gcc hello.o -o he

Nguồn: Hacker News

Đọc thêm từ Công nghệ