157

I learned from my colleague that one can write and execute a C program without writing a main() function. It can be done like this:

my_main.c

/* Compile this with gcc -nostartfiles */

#include <stdlib.h>

void _start() {
  int ret = my_main();
  exit(ret); 
}

int my_main() {
  puts("This is a program without a main() function!");
  return 0; 
}

Compile it with this command:

gcc -o my_main my_main.c –nostartfiles

Run it with this command:

./my_main

When would one need to do this kind of thing? Is there any real world scenario where this would be useful?

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
SimpleGuy
  • 2,764
  • 5
  • 28
  • 45
  • 1
    Remotely related: http://stackoverflow.com/questions/2548486/compiling-without-libc – Mohit Jain Apr 17 '15 at 09:09
  • 9
    Classic article that demonstrates some of the inner workings of how programs start up: [A Whirlwind Tutorial on Creating Really Teensy ELF Executables for Linux](http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html). This is a good read that discusses some of the finer points of `_start()` and other stuff outside of `main()`. –  Apr 17 '15 at 14:44
  • 1
    The C language itself says nothing about `_start`, or about any entry point other than `main` (except that the name of the entry point is implementation-defined for freestanding (embedded) implementations). – Keith Thompson Jun 01 '15 at 03:17
  • Note that this `_start` is unsafe, violating the ABI when it calls `my_main`; you tell the compiler it's a normal function, but actually it's entered with the stack-pointer already aligned (e.g. on x86-64, RSP % 16 == 0), not RSP % 16 == 8 like on entry to a normal function after a `call` that pushes an 8-byte return address. You can fix that with `__attribute__((force_align_arg_pointer))` for `_start` to tell GCC that the stack pointer may be "misaligned" on entry to that one "function", as shown in [Get arg values with inline asm without Glibc?](https://stackoverflow.com/a/50283880/224132) – Peter Cordes Dec 10 '20 at 00:38
  • On a modern Linux distro, this would lead to a crash if `my_main` used scanf at all, or printf (or any variadic function) with a `floar` or `double` FP arg. [glibc scanf Segmentation faults when called from a function that doesn't align RSP](https://stackoverflow.com/q/51070716) – Peter Cordes Dec 10 '20 at 00:40

4 Answers4

145

The symbol _start is the entry point of your program. That is, the address of that symbol is the address jumped to on program start. Normally, the function with the name _start is supplied by a file called crt0.o which contains the startup code for the C runtime environment. It sets up some stuff, populates the argument array argv, counts how many arguments are there, and then calls main. After main returns, exit is called.

If a program does not want to use the C runtime environment, it needs to supply its own code for _start. For instance, the reference implementation of the Go programming language does so because they need a non-standard threading model which requires some magic with the stack. It's also useful to supply your own _start when you want to write really tiny programs or programs that do unconventional things.

fuz
  • 88,405
  • 25
  • 200
  • 352
  • 2
    Another example is Linux's dynamic linker/loader which has its own _start defined. – P.P Apr 17 '15 at 10:52
  • 2
    @BlueMoon But that`_start` comes from the object file `crt0.o`, too. – fuz Apr 17 '15 at 10:56
  • Is this behavior in the language standard? I've seen it used with many embedded systems compilers (that don't use Linux). – Thomas Matthews Apr 17 '15 at 19:13
  • 2
    @ThomasMatthews The standard doesn't specify `_start`; in fact, it doesn't specify what happens before `main` is called at all, it just specifies what conditions must be met when `main` is called. It's more a convention for the entry point to be `_start` which dates back to the old days. – fuz Apr 17 '15 at 19:18
  • 1
    "the reference implementation of the Go programming language does so because they need a non-standard threading model" crt0.o is C specific (crt->C runtime). There's no reason to expect it to be used for any other language. And Go's threading model is completely standard compliant – Steve Cox Apr 17 '15 at 20:19
  • 9
    @SteveCox Many programming language are built on top of the C runtime because it's easier to implement languages this way. Go does not use the normal threading model. They use small, heap-allocated stacks and their own scheduler. This is certainly not a standard threading model. – fuz Apr 17 '15 at 20:34
  • 1
    relevant: http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html `A Whirlwind Tutorial on Creating Really Teensy ELF Executables for Linux (or, "Size Is Everything")` – SnakeDoc Apr 20 '15 at 15:50
  • I'd appreciate if you could clarify that program entry is environment-specific and primarily for hosted environments. It is not part of the standard (you commented that already, it seems to be a good idea to add that to your answer - comments are often ignored). For freestanding environments the C standard also allows more relaxed behaviour and does not mandate `main` or a specific signature. – too honest for this site Jun 14 '16 at 23:10
  • @Olaf This entire answer concerns an implementation detail of certain C runtimes. Maybe I should clarify that. – fuz Jun 14 '16 at 23:38
  • @FUZxxl: Hmm, I think that's the essence of my comment. So much for "think first, then write". Just please be precise about freestanding environments (and nothing about "normal"/"not normal". that hurts somewhat;-) – too honest for this site Jun 14 '16 at 23:42
54

While main is the entry point for your program from a programmers perspective, _start is the usual entry point from the OS perspective (the first instruction that is executed after your program was started from the OS)

In a typical C and especially C++ program, a lot of work has been done before the execution enters main. Especially stuff like initialization of global variables. Here you can find a good explanation of everything that's going on between _start() and main() and also after main has exited again (see comment below).
The necessary code for that is usually provided by the compiler writers in a startup file, but with the flag –nostartfiles you essentially tell the compiler: "Don't bother giving me the standard startup file, give me full control over what is happening right from the start".

This is sometimes necessary and often used on embedded systems. E.g. if you don't have an OS and you have to manually enable certain parts of your memory system (e.g. caches) before the initialization of your global objects.

MikeMB
  • 20,029
  • 9
  • 57
  • 102
  • 1
    The global vars are part of the data section and thus are setup during the loading of the program (if they are const they are part of the text section, same story). The _start function is completely unrelated to that. – Cheiron Apr 17 '15 at 13:33
  • @Cheiron: Sorry, my emistake In c++, global variables are often initialized by a constructor which is run inside `_start()` (or actually another function called by it) and in many Bare-Metal-Programs, you explicitly copy all global data from flash to RAM first, which also happens in `_start()`, but this question was neither about c++ nor bare-metal code. – MikeMB Apr 17 '15 at 15:04
  • 1
    Note that in a program that supplies its own `_start`, the C library won't get initialized unless you take special steps to do it yourself -- it may well be unsafe to use any non-async-signal-safe function from such a program. (There's no official guarantee that *any* library function will work, but async-signal-safe functions can't refer to any global data at all, so they'd have to go out of their way to malfunction.) – zwol Apr 17 '15 at 17:59
  • @zwol that's only partially correct. For instance, such a function might allocate memory. Allocating memory is problematic when the internal data structures for `malloc` are not initialized. – fuz Apr 17 '15 at 19:20
  • @FUZxxl Async-signal-safe functions are *not* allowed to call `malloc`. This is not explicitly stated in [the standard](http://pubs.opengroup.org/onlinepubs/9699919799/), but follows directly from `malloc` itself not being in the async-signal-safe list. – zwol Apr 17 '15 at 19:37
  • @zwol This is incorrect. The list is merely the guaranteed minimum. A platform could provide an async-signal-safe `malloc` and the implementation may use it. In fact, I belive that the glibc provides such a `malloc`. – fuz Apr 17 '15 at 19:38
  • 1
    @FUZxxl Having said that, I notice that async-signal-safe functions *are* allowed to modify `errno` (e.g. `read` and `write` are async-signal-safe and can set `errno`) and that could conceivably be a problem depending on exactly when the per-thread `errno` location is allocated. – zwol Apr 17 '15 at 19:39
  • 1
    @FUZxxl I know for a fact that glibc's `malloc` is *not* async-signal-safe, but I accept the correction as to the abstract case. – zwol Apr 17 '15 at 19:42
  • @zwol If you say so, I'm just guessing. – fuz Apr 17 '15 at 19:49
  • I don't think it's uncommon for C compilers, especially those targeting embedded systems, to have the code image contain a the initialized data in a "compressed" form and have startup code uncompress it. I've not seen anything particularly fancy in this regard, but I have seen compilers that generate a sequence of concatenated `{unsigned char *dest; unsigned short n; unsigned char data[n]; }` structures. This can be somewhat helpful in many cases, and very helpful in a few, and need not cost much even when it doesn't help. – supercat Apr 17 '15 at 20:12
14

Here is a good overview of what happens during program startup before main. In particular, it shows that __start is the actual entry point to your program from OS viewpoint.

It is the very first address from which the instruction pointer will start counting in your program.

The code there invokes some C runtime library routines just to do some housekeeping, then call your main, and then bring things down and call exit with whatever exit code main returned.


A picture is worth a thousand words:

C runtime startup diagram


P.S: this answer is transplanted from another question which SO has helpfully closed as duplicate of this one.

ulidtko
  • 14,740
  • 10
  • 56
  • 88
  • 1
    Cross-posted to preserve the excellent [analysis](http://dbp-consulting.com/tutorials/debugging/linuxProgramStartup.html) and the nice picture. – ulidtko Jan 20 '20 at 09:14
2

When would one need to do this kind of thing?

When you want your own startup code for your program.

main is not the first entry for a C program, _start is the first entry behind the curtain.

Example in Linux:

_start: # _start is the entry point known to the linker
    xor %ebp, %ebp            # effectively RBP := 0, mark the end of stack frames
    mov (%rsp), %edi          # get argc from the stack (implicitly zero-extended to 64-bit)
    lea 8(%rsp), %rsi         # take the address of argv from the stack
    lea 16(%rsp,%rdi,8), %rdx # take the address of envp from the stack
    xor %eax, %eax            # per ABI and compatibility with icc
    call main                 # %edi, %rsi, %rdx are the three args (of which first two are C standard) to main

    mov %eax, %edi    # transfer the return of main to the first argument of _exit
    xor %eax, %eax    # per ABI and compatibility with icc
    call _exit        # terminate the program

Is there any real world scenario where this would be useful?

If you mean, implement our own _start:

Yes, in most of the commercial embedded software I have worked with, we need to implement our own _start regarding to our specific memory and performance requirements.

If you mean, drop the main function and change it to something else:

No, I don't see any benefit doing that.

Van Tr
  • 5,889
  • 2
  • 20
  • 44
  • `_start` should usually call `exit`, not `_exit` (if you're linking libc at all) to make sure stdio buffers get flushed. e.g. if you redirect stdout to a file, `puts("hello")` will still be buffered when main returns because stdout will be full-buffered. This also calls `atexit` functions that have been registered as well. – Peter Cordes Dec 10 '20 at 00:50
  • A full-featured `_start` would also have checked RDX on entry and if non-NULL registered that with `atexit`. It's a callback that the dynamic linker makes non-NULL if any libraries had any destructors. (Static executables enter their own `_start` directly from the kernel with all registers 0, with no chance for libraries to have startup code run first. That's how glibc inits itself in a dynamically linked executable even if _start doesn't call its functions. With this _start in a static executable, glibc functions like printf would crash; `FILE *stdout` wouldn't even be initialized.) – Peter Cordes Dec 10 '20 at 00:54