2

In an embedded microcontroller programmed using C, generally the code body is placed within a continuous loop like while(1) in the main function so execution never stops.

What happens if we take out this while(1) loop and allow the processor of the microcontroller to reach the return 0 line, breaking out of the main function?

Will this halt the CPU or will the compiler do something else during compilation to allow for this to happen by looping through some type of handler where it appears to be doing nothing but is in fact just looping through some useless code area?

JRE
  • 67,678
  • 8
  • 104
  • 179
David777
  • 1,548
  • 12
  • 29
  • 1
    Generally the program crashes or you get runaway code. Some hobbyist platforms might place an extra for loop in the CRT outside main(), because they suspect their users to be beginners/PC programmers who have a tendency to write their usual incorrect PC programming `int main (void)`. Almost every single bare metal (freestanding) system uses `void main (void)` as an implementation-defined form and does not return from main(). In case you are using the gcc compiler, you have to compile with `-ffreestanding` to take it out of "PC mode". – Lundin Feb 23 '23 at 09:31
  • @Lundin 2.) _"hobbyist platforms might place an extra for loop in the CRT outside main(), because they suspect their users to be beginners"_ I would argue that any respectable designer would guard against the unexpected. – Velvel Feb 23 '23 at 10:49
  • 1
    @Velvel Yeah by writing an eternal for loop in main()... Respectable designers guard against unexpected _program behavior_ not unexpected _programmer behavior_. – Lundin Feb 23 '23 at 10:56
  • Unexpected program behavior isn't something that magically happens, without the _help_ of a human. – Velvel Feb 24 '23 at 07:10
  • @Velvel Or external factors like EMI. Or silicon bugs. Or tool bugs. There's a difference between "I used engineer best practices but still it failed" and "I don't know/care to learn about best practices and just hack away". The former is solved by defensive programming, MCU hardware support like safety MCUs, ECC and similar. The latter is solved by having a competent software engineer writing the software instead of some PC programmer or Arduino quack. – Lundin Feb 24 '23 at 09:37

2 Answers2

5

Depends on the startup code in the C library used for the MCU.

Main is just a normal function like any other, it is just called by the startup code.

When main exits, it returns to the startup code that called it.

That is it, no surprises there, but what happens then is unknown.

The startup code may continue by doing something, which could be anything, or even nothing.

So you should not assume what happens or does not happen.

Generally, it makes no sense to return from main in an embedded MCU program.

If you do need to stop executing code or restart from main when finished, write your own function to deinitialize the system safely and go into endless loop, maybe blinking a LED for indicating it, or trigger a hardware reset via watchdog or whatever you want.

Options may include:

  1. The startup code does not support returning from main and random code gets executed.

  2. The startup code handles returning from main by entering an endless loop. Interrupts may be left on so background tasks such as interrupts continue working, or they may be disabled. IO ports and peripherals likely stay as they were.

  3. The startup code handles returning from main by jumping back to startup code so basically program execution starts like after hardware reset, but with the exception that peripherals and IO ports etc stay enabled, so this may not work as a reboot unless peripherals are reset.

The point is, if you do return from main, you must know what will happen in your project by figuring out the library startup code, as there is no single answer.

brhans
  • 14,373
  • 3
  • 34
  • 49
Justme
  • 127,425
  • 3
  • 97
  • 261
  • Case 2 may also be a loop around some kind of HALT, IDLE or SLEEP instruction, where the MCU does nothing, but waits for interrupts. Same result, except it uses much less power. No idea if any start code out there does that, though. Case 3 may also involve actually performing a reset of some sort. – jcaron Feb 23 '23 at 23:44
2

generally the code body is placed within a continuous loop like while(1) in the main function so execution never stops.

The fact that it's called main is not the norm; it's the rule when you have a hosted environment (i.e. something running a fully fledged operating system with a C interface). It's a mere convention anywhere else (see: C std 5.1.2.1 153).

Equally typically, the entry point sets up the necessary hardware, interrupts, timer tasks and then just halts, or hands control back to the RTOS who then goes into an event-driven scheduling, instead of an infinite loop (or an infinite loop that handles events, but not a while(1) loop that the firmware programmer writes themselves).

So what happens if we take out this while(1) loop and allow the processor of the MCU to reach the return 0 line, breaking out of the main function?

That's up to the implementation of the compiler/platform:

The effect of program termination in a freestanding environment is implementation-defined.

This might just lead to a spinlock, a halt instruction, an illegal instruction, a software interrupt, or a reset, or whatever else the developers of your tool chain come up with. All equally valid, and I saw at least the spinlock, the halt and the reset in the wild.

Marcus Müller
  • 88,280
  • 5
  • 131
  • 237
  • "The fact that it's called main is not the norm;" Well, it is. There's the entry point of the program and there's the C construct of main(). On a freestanding system, the entry point is some reset vector. But everything carried out from there until main() is called isn't really regarded as "a C program". C has requirements stating that misc initialization must be carried out before main() is called and that holds true for freestanding systems too. Since most are flash-based, it is not possible to initialize by loading everything into RAM from an OS like on a PC. -> – Lundin Feb 23 '23 at 09:41
  • So there is a need to make a distinction between the true (power-on reset) entry point of the program and main(). – Lundin Feb 23 '23 at 09:41
  • 3
    @Lundin yes, on hosted platforms, that's true: not so on freestanding platforms: there's no `main` there. What code gets executed on start is up to the implementation to define. The existence of `main` is a pure convention, and I've seen platforms (Renesas) where that's not the case. (and where a lot of the stack setup etc that's typically already done when a "conventional" main is entered needs to be done manually :/ ) – Marcus Müller Feb 23 '23 at 09:58
  • Yeah well it is technically possible to write a freestanding system without main(), if you have no intention of relying on static storage initialization anyway, or do such initialization in a different way than what's outlined by standard C. In which case you are relying on non-standard extensions rather than the requirements on a "conforming freestanding implementation" (formal term). Related article: [How to perform initialization of static storage variables in embedded systems?](https://electrical.codidact.com/posts/287398) – Lundin Feb 23 '23 at 10:09
  • 3
    indeed, in a freestanding impl, you'd still expect static variables to be initialized, function pointers to be reachable and all these things – but you often need user code to even do something like initializing external program or constant storage or even DRAM. So, there's kind of both: situations where you just use the startup code your platform typically gives you, and just write your code into a `main` (or another similar function), or platforms where you first need to massage things manually a bit until your average C function can work as expected.Then of course,there's the worst of both. – Marcus Müller Feb 23 '23 at 10:31