3
$\begingroup$

I noticed C defines the entry point as a main() function. So does Java. So does C++ (or not, because functions called as global variable initializers are called before main(). I was wondering. Is there a reason for requiring a user-defined function to be the entry point of the program? Is this (typically) how executable work, or is this just a higher level language requirement? Could a language be designed that does not use a function as an entry point, that executes very slightly faster because the function-call overhead of calling main() is eliminated?

$\endgroup$
11
  • 1
    $\begingroup$ It's not just "low level" languages that do it. Java, Kotlin, and Rust I would not consider low level, but they all use a main function $\endgroup$
    – Seggan
    Commented May 26, 2023 at 19:39
  • $\begingroup$ Haskell too I think $\endgroup$
    – Seggan
    Commented May 26, 2023 at 19:45
  • 3
    $\begingroup$ This is essentially a duplicate of What are the advantages and disadvantages of only allowing top level declarations? ─ if you allow only declarations at the top level then your program's entry point must likewise be a declaration (e.g. a function declaration). Inversely, if you allow statements at the top level then the top-level statements must be the entry point because "when the program starts" is the only sensible time to execute those statements. $\endgroup$
    – kaya3
    Commented May 26, 2023 at 20:27
  • 3
    $\begingroup$ I don't think this is a dupe of the proposed question. This is asking about the performance penalty of calling a function at the start of a program (and makes the assumption, which I had assumed not to be the case, that main is physically called using something like a call instruction). Whether or not the assumption underlying this question is correct, and the tradeoffs of a language where that is true, are both independent of the dupe target and interesting in their own right. $\endgroup$ Commented May 26, 2023 at 21:39
  • 1
    $\begingroup$ IMO this question has more to do with low-level semantics while the other one is about syntax. For instance I don't think any of the current answers can be meaningfully moved. $\endgroup$
    – Longinus
    Commented May 27, 2023 at 2:35

2 Answers 2

7
$\begingroup$

This depends both on language design as well as, more broadly, operating system design.

Many languages such as Python, PHP, Ruby or ECMAScript, but also Swift, C# or Holy C, as well as many implementations, do not require routines as entry points and instead start "executing from the top of a file" - whatever that means according to each of those.

function hello(string $s)
{
    echo("Hello " . $s . "!");
}

hello("World!"); //interpreter just runs that

Others have as entry point the computation of a variable or constant.
Haskell is notable there: it has a main constant of some IO a type.

hello :: String -> IO ()
hello s = putStrLn $ "Hello " ++ s ++ "!"

main :: IO ()
main = hello "World"

Note how hello is a function but main is not.

But those rules are established by programming languages.
Operating systems and language implementations may disagree and come up with their own sets of rules.
Languages, OSs and implementations may or may not be compatible with each other.

For instance C and C++ both are specified in terms of two distinct environments: Hosted and Freestanding.
In Hosted environment, you have access to the full standard libraries and must have a main routine as entry point, while in Freestanding environment, only a subset of the stdlibs are exposed and whether or not an entry point is necessary at all is implementation-defined (C++20§6.9.3.1.).

C++ also goes out of its way to give many rules to main including:

  • it may not be predefined,
  • it may not be overloaded,
  • it must have a "type with C++ language linkage,"
  • its actual linkage is implementation-defined,
  • it must return an int,
  • that returned int is implicitly 0 if not written by the user,
  • main's type is implementation-defined,
  • an implementation must allow at least () and (int, char**) as parameters,
  • if present, argc must be the length of a vector pointed to by argv minus one,
  • argv[argc] must be 0,
  • nowhere in a program may main be "used."

So as you can see, many implementation-defined properties.

Meanwhile, compilers for MS Windows extend C and C++ with additional entry points that the Windows ecosystem rely on and that are exposed to the user.
Notably,

  • main is called by "console applications,"
  • WinMain is called by "GUI applications,"
  • wWinMain is the same as WinMain but accepts 16bits characters as input,
  • DllMain is called by DLLs at load time, unload time, and when threads spawn and die while the DLL is linked in.

UNIX and Win32 also allow main a third parameter of type char** (envp) and Darwin allows it a fourth of the same type (apple).

Then you also have Java codebases which may contain multiple main static methods stored in different classes, and where you have to specify which class to load main from - done either from the shell or by having a Main-Class entry in a JAR's manifest.

Is this (typically) how executable work, or is this just a higher level language requirement?

Typically, an executable has metadata that contains an address of where to start executing code.
In ELF that is e_entry at offset 0x18.
In PE that is AddressOfEntryPoint at offset 0x28.

Also note that we have to take into account the workings of runtime system libraries (RTSs).
For instance GNU works by having entry points invoke __libc_start_main which not only calls main but also runs routines from the .init and .fini sections, as well as sees pointers in the .preinit_array, .init_array, and .fini_array sections called by the dynamic linker.
https://www.sco.com/developers/gabi/latest/ch5.dynamic.html#init_fini
https://maskray.me/blog/2021-11-07-init-ctors-init-array

As far as executables for x86-64, ARM, and other typical architectures go, there is no concept of "function." Only code, addresses, and jumps.

Could a language be designed that does not use a function as an entry point, that executes very slightly faster because the function-call overhead of calling main() is eliminated?

That is a purely compiler-related question, not a language-related one.
A compiler is free to inline everything inside _start and to eliminate all calls if it wants to. It can also eliminate all jumps, and in fact on x86-64, you can use only mov if you wish to.

$\endgroup$
2
$\begingroup$

The alternative to collecting all the main code in a function is having it at the top level of your source files. While there is a executable implementation reason to have main be its own thing (see, for example, crt0.s and OSDev's Creating a C Library), a very useful advantage of having a main function is knowing that the main program logic all lives in the same place. While for small scripts it may be useful to just put code in the root of the file (and most scripting languages do allow to do that), as a codebase gets larger it becomes more difficult to find where the program starts.

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .