Compilar y ejecutar el programa sin main () en C

79

Estoy tratando de compilar y ejecutar el siguiente programa sin main()función en C. He compilado mi programa usando el siguiente comando.

Y el compilador da una advertencia

Está bien, no hay problema. luego, he ejecutado el archivo ejecutable (a.out), ambas printfdeclaraciones se imprimen correctamente y luego obtengo una falla de segmentación .

Entonces, mi pregunta es, ¿Por qué falla la segmentación después de ejecutar con éxito declaraciones de impresión?

mi código:

salida:

Hello World...
Successfully run without main...
Segmentation fault (core dumped)

Nota:

Aquí, el -nostartfilesindicador gcc evita que el compilador use archivos de inicio estándar al vincular

msc
fuente
37
Me sorprende que esto funcione en absoluto. Francamente, considero que este tratamiento por parte del vinculador es erróneo (o al menos algo malo): no había un punto de entrada, por lo que el vinculador simplemente lo alucinó desde cualquier función que tuviera a mano. Blech.
imallett
4
@imallett, al menos el enlazador tuvo la amabilidad de llamar la atención sobre él con una advertencia y explicar qué acción de respaldo estaba tomando. Sin embargo, tiene razón en que esto podría ser mejor como un error que solo como una advertencia.
Toby Speight
¿Por qué no usarías main?
Pieter B
4
@PieterB: no es demasiado relevante para una discusión sobre unices, pero el punto de entrada para los programas de Windows no es necesariamente main, pero WinMaino wWinMain.
StoryTeller - Unslander Monica
@StoryTeller en realidad tanto en Windows como en Linux, puede establecer un punto de entrada arbitrario: para Linux ldsería una -eopción, para el enlazador MSVC de Windows sería una /ENTRYopción.
Ruslan

Respuestas:

131

Echemos un vistazo al ensamblaje generado de su programa:

Tenga en cuenta la retdeclaración. El punto de entrada de su programa está determinado a ser nomain, todo está bien con eso. Pero una vez que la función regresa, intenta saltar a una dirección en la pila de llamadas ... que no está poblada. Eso es un acceso ilegal y sigue una falla de segmentación.

Una solución rápida sería llamar exit()al final de su programa (y asumiendo C11 también podríamos marcar la función como _Noreturn):

De hecho, ahora su función se comporta como una mainfunción normal , ya que después de regresar de main, la exitfunción se llama con mainel valor de retorno.

StoryTeller - Unslander Monica
fuente
6
Creo que hay algunas combinaciones de arquitectura / sistema operativo en las que puedes simplemente "regresar" de un programa; ¿Ejecutables MS-DOS .COM? De todos modos, estamos muy interesados ​​en el comportamiento específico de la implementación.
pjc50
4
@ pjc50 - De hecho, lo somos. Aunque la ruta en el OP sugirió una variante de Unix. Eso, junto con la popularidad de ciertas arquitecturas y conjuntos de instrucciones, fue la única razón por la que me sentí cómodo al presentar el ensamblaje generado en la respuesta.
StoryTeller - Unslander Monica
1
Solo una observación. -nostartfilestambién puede inutilizar la biblioteca C. Sin el inicio de C ejecutado, las llamadas posteriores a las funciones de la biblioteca de C pueden fallar inesperadamente. En Linux, si tuviera que compilar -nostartupfilesy -staticpuede descubrir que el programa fallará. Hay bibliotecas de C como MUSL que no requieren inicialización inicial que están diseñadas para funcionar en este entorno.
Michael Petch
22

En C, cuando se llaman funciones / subrutinas, la pila se completa como (en el orden):

  1. Los argumentos,
  2. Dirección del remitente,
  3. Variables locales, -> parte superior de la pila

siendo main () el punto de inicio, ELF estructura el programa de tal manera que las instrucciones que vienen primero se presionan primero, en este caso printfs.

Ahora, el programa está algo truncado sin la dirección de retorno O __end__y, de hecho, asume que lo que esté en la pila en esa __end__ubicación ( ) es la dirección de retorno, pero desafortunadamente no lo es y, por lo tanto, se bloquea.

Milind Deore
fuente
4
¿El orden de los datos de pila está definido por el estándar C? Pensé que
dependía de la
1
Es por eso que mencioné ELF (formato de archivo ejecutable y enlazable), esto se genera mediante la compilación cruzada para un tipo de ARCH específico en el sistema operativo requerido.
Milind Deore
1
Para ser exigente, puede utilizar el formato ELF incluso en sistemas sin pila. Un ejemplo de tal sistema es Freescale RS08 con el compilador Codewarrior, que genera archivos de enlace ELF.
Lundin