42
\$\begingroup\$

I know that in computers, value returned by the main() function is received by the operating system. But, what happens in the main() function of a microcontroller?

\$\endgroup\$
3
  • 9
    \$\begingroup\$ I always use void main() when I am using C for PIC microcontrollers. When using C compilers for microcontrollers, it really does not matter at all. Because there is no operating system that runs the (say) "main.c". If there is something like RTOS running in that microcontroller, then the operating system is the "main.c". \$\endgroup\$ Commented Jan 22, 2013 at 16:44
  • 7
    \$\begingroup\$ Not realy a duplicate, but at least related: electronics.stackexchange.com/q/30830/4950 \$\endgroup\$
    – PetPaulsen
    Commented Jan 22, 2013 at 17:15
  • 2
    \$\begingroup\$ How the startup function is defined is usually not up to you to decide. The environment you're using will document the supported startup function forms. Hosted C implementations are required to support two forms of main with two different signatures, both of which return int. If you're using a freestanding C implementation, that implementation dictates how you should write the startup function. You can't write a void returning function just because it doesn't return. The behavior of not returning is different from the function type which influences the overall calling conventions. \$\endgroup\$
    – Kaz
    Commented Jan 23, 2013 at 0:43

5 Answers 5

48
\$\begingroup\$

On a microcontroller, main() is not really expected to ever exit, and the behavior if it does is not defined — so it's up to whoever wrote the C runtime for the microcontroller. I've seen systems that:

  • Have an implicit loop around main(), so that if it exits, it simply gets called again.
  • Have a simple "jump-to-self" loop (or a HALT instruction) that gets executed if main() ever exits.
  • Simply execute the rest of code memory that follows the call to main(). This is called "running off into the weeds".

I've never seen one that actually does anything at all with the value returned by main(). If this is something you actually care about, then you should take a look at — and possibly modify — the source code for your system's C runtime library.

\$\endgroup\$
10
  • 11
    \$\begingroup\$ The C standard defining main() to have an int return value obviously was not designed with an OS-less microcontroller in mind. So this is unspecified behaviour and anything might happen depending on your C runtime, as Dave listed. \$\endgroup\$
    – ndim
    Commented Jan 22, 2013 at 17:32
  • 4
    \$\begingroup\$ C running on an OS-less microcontroller could be considered a freestanding implementation, and the C standard doesn't even require a freestanding environent to have a main(), much less define its return value. That's up to the implementor. \$\endgroup\$
    – KutuluMike
    Commented Jan 22, 2013 at 21:05
  • 2
    \$\begingroup\$ @ndim - to split hairs, void main( void ) is implementation defined behaviour, not unspecified behaviour. \$\endgroup\$
    – Andrew
    Commented Jan 22, 2013 at 21:42
  • 4
    \$\begingroup\$ @MichaelEdenfield: Indeed. However, all code in C is defined in terms of functions, so it is never possible to have a fresstanding system completely written in C; there needs to be at least a tiny bit of assembly language (or whatever) that sets up a minimal environment so that a C function can be called. The most obvious name for that function is main(). \$\endgroup\$
    – Dave Tweed
    Commented Jan 22, 2013 at 22:08
  • 1
    \$\begingroup\$ @istepaniuk: That's equivalent to my second bullet. Not all microcontrollers have HALT instructions. \$\endgroup\$
    – Dave Tweed
    Commented Apr 4, 2020 at 16:07
8
\$\begingroup\$

A common misunderstanding/myth is that int main is the only valid form specified by the standard. That is not true.

The C standard speaks of two implementations: hosted and freestanding. "Implementation" in this case means compiler. Hosted compilers compile for a specific OS and freestanding compilers compile for a specific bare metal application. Embedded systems are almost always freestanding systems - even in the case of RTOS.

Freestanding implementations may use any form for main(), they don't even need to have a function called main. Most often, they use the form void main (void), since it doesn't make any sense to return anything.

What's important to realize here is that it is always the compiler that decides the form of main() and never the programmer.

Freestanding implementations that do return something from main() are very questionable. Makes you wonder if the people who made the compiler actually read the standard...

For those using gcc, make sure to compile as -ffreestanding or otherwise the compiler defaults to "PC mode" (hosted). This will also impact whether the compiler inlines standard library functions or not, which you sometimes wish to avoid in high integrity embedded systems.

Details here.

\$\endgroup\$
4
\$\begingroup\$

The C language standard allows for the implementation defined variation void main( void ) and this is the usual form in embedded systems - simply because they are not expected to return.

If you look at the compiler setup, there is usually a bootstrap snippet of code, called from the reset vector, which performs some basic initialisation (including eg coping of initialisation values into variables) before calling main().

This will also (usually) be within an infinite loop, or perhaps perform a reset, if main() returns

\$\endgroup\$
1
\$\begingroup\$

You cant get to C without a bootstrap the bootstrap code, ideally written in asm (if in C you have a chicken and egg problem), prepares the environment to run C sets up a stack, preps .data, preps .bss, then calls main. Some folks think main never returns but the beauty of baremetal is that is all up to you. You can certainly return from main in an mcu, say for example when something fatal happens, or if your design is 100% event (interrupt) driven, setup all the peripherals and interrupts then return and have your bootstrap spin in an infinite loop which is prettier than a while(1) continue; in the main() code.

A lot of folks use canned libraries and environments to make baremetal not feel like baremetal and while are responsible for what is going on underneath dont look to see nor control it. Again part of the beauty of baremetal.

What happens when main returns depends on the system be it an operating system or baremetal on an mcu. A wise design would allow for main to return in the bootstrap and then depending on that design choose what to do, at a minimum go into an infinite loop.

main() is just another function name to the compiler for the most part, some toolchains will add extra stuff when it sees that function so you should use that function with care in baremetal to avoid excess baggage in such a resource restrained environment. Not unwise to use a C entry point other than the name main() to avoid that problem, but naturally that usually involves taking control over the whole thing rather than just adding a few lines to someone elses sandbox (writing your own bootstrap and possibly linker script and as a result possibly the libraries (not a bad idea) as well).

Once you take over you can certainly have the return value from main mean something, again in the case of an interrupt driven design main() could simply start things in motion and then return to an infinite or wait for interrupt loop in the bootstrap, but you could choose to have the return value cause action to be taken if not zero then clock gate or put all peripherals in reset for a low power failed startup.

Short answer: The return value goes into the ether as generally you land in an infinite loop either directly or after the bootstrap has assumed something bad happened and shut down or un-done whatever you configured in your portion of the application. Any sandbox that doesnt allow for main() to return is a sandbox to avoid using, it should at least have minimal support (an infinite loop).

Shorter answer: Who receives the return value? You do as you are responsible for this application and as a result the bootstrap.

\$\endgroup\$
0
\$\begingroup\$

It (as other answers mentioned) depends on your toolchain, but for example in GCC main is compiled as other functions, so its return value will be stored accordingly to calling conventions (on ARM I am using right not with GCC it will be put to R0 just before return).

I guess that is is simillar on AVR-GCC, so custom script can use this value after main returns.

\$\endgroup\$
2
  • \$\begingroup\$ this rather misses the point \$\endgroup\$ Commented Aug 8, 2014 at 22:02
  • \$\begingroup\$ It emphasizes that the one who calls main can get its return value. Of course it is ignored in 99.9% situations, but answer provides information who can receive this return value. \$\endgroup\$ Commented Aug 8, 2014 at 22:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.