¿Quién recibe el valor devuelto por main()?

Sé que en las computadoras, main()el sistema operativo recibe el valor devuelto por la función. Pero, ¿qué sucede en la main()función de un microcontrolador?

Siempre uso void main() cuando uso C para microcontroladores PIC. Cuando se usan compiladores de C para microcontroladores, realmente no importa en absoluto. Porque no hay ningún sistema operativo que ejecute (digamos) "main.c". Si hay algo como RTOS ejecutándose en ese microcontrolador, entonces el sistema operativo es el "main.c".
No es realmente un duplicado, pero al menos está relacionado: electronics.stackexchange.com/q/30830/4950
Por lo general, no depende de usted decidir cómo se define la función de inicio. El entorno que está utilizando documentará los formularios de función de inicio admitidos. Se requieren implementaciones de C alojadas para admitir dos formas de maincon dos firmas diferentes, las cuales devuelven int. Si está utilizando una implementación de C independiente, esa implementación dicta cómo debe escribir la función de inicio. No puede escribir una voidfunción de retorno solo porque no regresa. El comportamiento de no regresar es diferente del tipo de función que influye en las convenciones generales de llamadas.

Respuestas (5)

En un microcontrolador, main()realmente no se espera que salga nunca, y el comportamiento si lo hace no está definido, por lo que depende de quien haya escrito el tiempo de ejecución de C para el microcontrolador. He visto sistemas que:

  • Tener un bucle implícito alrededor main()de , de modo que si sale, simplemente se vuelve a llamar.
  • Tenga un bucle simple de "salto a sí mismo" (o una instrucción HALT) que se ejecute si main()alguna vez sale.
  • Simplemente ejecute el resto de la memoria de código que sigue a la llamada a main(). Esto se llama "correr hacia la maleza".

Nunca he visto uno que realmente haga algo con el valor devuelto por main(). Si esto es algo que realmente le importa, entonces debería echar un vistazo, y posiblemente modificar, el código fuente de la biblioteca de tiempo de ejecución C de su sistema.

Me ganaste. +1 por sincronicidad.
El estándar C que define main()tener un intvalor de retorno obviamente no fue diseñado con un microcontrolador sin sistema operativo en mente. Entonces, este es un comportamiento no especificado y cualquier cosa podría suceder dependiendo de su tiempo de ejecución de C, como enumeró Dave.
C que se ejecuta en un microcontrolador sin sistema operativo podría considerarse una implementación independiente, y el estándar C ni siquiera requiere un entorno independiente para tener un main()valor de retorno, y mucho menos definirlo. Eso depende del implementador.
@ndim: para dividir los pelos, void main( void )es un comportamiento definido por la implementación , no un comportamiento no especificado .
@MichaelEdenfield: Efectivamente. Sin embargo, todo el código en C se define en términos de funciones, por lo que nunca es posible tener un sistema autónomo completamente escrito en C; debe haber al menos un poco de lenguaje ensamblador (o lo que sea) que configure un entorno mínimo para que se pueda llamar a una función C. El nombre más obvio para esa función es main().
@ndim El estándar C dice que cualquier forma de main está bien. ¿Has leído la norma? Más importante aún, ¿ha verificado en qué subcapítulo se encuentra la sección que especifica el intformato main? 5.1.2.2 Entorno alojado.
Al menos mi código de inicio procederá a llamar a los destructores globales para que todo lo que se escriba en la salida estándar se borre.
Para explicar el comentario anterior de @Lundin: el estándar C conoce un "entorno alojado" (es decir, el programa que se ejecuta correctamente en un sistema operativo adecuado) donde devolverá un main(...) archivoint . Y luego está el "entorno independiente" (es decir, el programa que se ejecuta básicamente en bare metal) donde "el nombre y el tipo de la función llamada al inicio están definidos por la implementación". Entonces, todo vale en el entorno de su microcontrolador, y será mejor que observe lo que el código de inicio incluido en el binario generado a partir de su código fuente realmente hace con un desensamblador.
@ndim Incluso en una implementación alojada (que probablemente sea irrelevante para la mayoría de las preguntas de EE en primer lugar), se permite declarar main en formularios definidos por la implementación. El estándar no es muy claro. Detalles aquí
También me encontré con una instrucción HALT después del salto a main, si regresas, la cosa se congela.
@istepaniuk: Eso es equivalente a mi segunda viñeta. No todos los microcontroladores tienen instrucciones HALT.

Un malentendido/mito común es que int maines la única forma válida especificada por el estándar. Eso no es verdad.

El estándar C habla de dos implementaciones: alojada e independiente. "Implementación" en este caso significa compilador. Los compiladores alojados compilan para un sistema operativo específico y los compiladores independientes compilan para una aplicación bare metal específica. Los sistemas integrados casi siempre son sistemas independientes, incluso en el caso de RTOS.

Las implementaciones independientes pueden usar cualquier forma para main(), ni siquiera necesitan tener una función llamada main. La mayoría de las veces, usan el formulario void main (void), ya que no tiene ningún sentido devolver nada.

Lo que es importante darse cuenta aquí es que siempre es el compilador el que decide la forma main()y nunca el programador.

Las implementaciones independientes que devuelven algo main()son muy cuestionables. Te hace preguntarte si las personas que hicieron el compilador realmente leyeron el estándar...

Detalles aquí .

El estándar del lenguaje C permite la variación definida por la implementación void main( void )y esta es la forma habitual en los sistemas integrados, simplemente porque no se espera que regresen.

Si observa la configuración del compilador, generalmente hay un fragmento de código de arranque, llamado desde el vector de reinicio, que realiza una inicialización básica (que incluye, por ejemplo, convertir los valores de inicialización en variables) antes de llamar a main().

Esto también (generalmente) estará dentro de un bucle infinito, o tal vez realice un reinicio, si main()regresa

No puede llegar a C sin un arranque El código de arranque, idealmente escrito en asm (si en C tiene un problema del huevo y la gallina), prepara el entorno para ejecutar C configura una pila, prepara .data, prepara .bss, luego llama principal. Algunas personas piensan que main nunca regresa, pero la belleza de baremetal es que todo depende de ti. Sin duda, puede regresar de main en un mcu, digamos, por ejemplo, cuando sucede algo fatal, o si su diseño está 100% impulsado por eventos (interrupciones), configure todos los periféricos e interrupciones, luego regrese y haga que su arranque gire en un ciclo infinito que es más bonito que un rato(1) continuar; en el código principal().

Mucha gente usa bibliotecas y entornos enlatados para hacer que baremetal no se sienta como baremetal y, aunque son responsables de lo que sucede debajo, no miren para verlo ni controlarlo. De nuevo parte de la belleza del baremetal.

Lo que sucede cuando los retornos principales dependen del sistema, ya sea un sistema operativo o baremetal en una mcu. Un diseño inteligente permitiría que main regrese en el arranque y luego, dependiendo de ese diseño, elija qué hacer, como mínimo entrar en un ciclo infinito.

main() es solo otro nombre de función para el compilador en su mayor parte, algunas cadenas de herramientas agregarán cosas adicionales cuando vea esa función, por lo que debe usar esa función con cuidado en baremetal para evitar el exceso de equipaje en un entorno con recursos tan restringidos. No es imprudente usar un punto de entrada de C que no sea el nombre main() para evitar ese problema, pero, naturalmente, eso generalmente implica tomar el control de todo en lugar de simplemente agregar unas pocas líneas a la caja de arena de otra persona (escribir su propio programa de arranque y posiblemente el enlace). script y, como resultado, posiblemente también las bibliotecas (no es una mala idea).

Una vez que tome el control, ciertamente puede hacer que el valor de retorno de main signifique algo, nuevamente en el caso de un diseño impulsado por interrupción, main() podría simplemente iniciar las cosas en movimiento y luego volver a un infinito o esperar un ciclo de interrupción en el arranque, pero puede optar por que el valor de retorno provoque que se tomen medidas si no es cero, entonces la puerta del reloj o poner todos los periféricos en reinicio para un arranque fallido de baja potencia.

Respuesta corta: el valor de retorno va al éter, ya que generalmente aterriza en un bucle infinito, ya sea directamente o después de que el arranque haya asumido que sucedió algo malo y apague o deshaga lo que haya configurado en su parte de la aplicación. Cualquier sandbox que no permita que main() regrese es un sandbox para evitar su uso, al menos debería tener un soporte mínimo (un bucle infinito).

Respuesta más corta: ¿Quién recibe el valor de retorno? Usted hace como responsable de esta aplicación y, como resultado, el arranque.

Depende (como se mencionó en otras respuestas) de su cadena de herramientas, pero, por ejemplo, en GCC mainse compila como otras funciones, por lo que su valor de retorno se almacenará de acuerdo con las convenciones de llamadas (en ARM que estoy usando, no con GCC, se pondrá en R0 justo antes del regreso).

Supongo que es similar a AVR-GCC, por lo que el script personalizado puede usar este valor después de los retornos principales.

esto más bien pierde el punto
Destaca que el que llama mainpuede obtener su valor de retorno. Por supuesto, se ignora en el 99,9 % de las situaciones, pero la respuesta proporciona información sobre quién puede recibir este valor de retorno.