Theodoros Chatzigiankis
Yo vi un fragmento de código en CodeGolf que pretende ser una bomba de compilación, donde main
se declara como una gran matriz. Probé la siguiente versión (sin bomba):
int main[1] = { 0 };
Parece compilar bien bajo Clang y con solo una advertencia bajo GCC:
advertencia: ‘principal’ suele ser una función [-Wmain]
El binario resultante es, por supuesto, basura.
Pero, ¿por qué se compila en absoluto? ¿Está incluso permitido por la especificación C? La sección que creo que es relevante dice:
5.1.2.2.1 Inicio del programa
La función llamada al inicio del programa se llama main. La implementación no declara ningún prototipo para esta función. Se definirá con un tipo de retorno de int y sin parámetros. […] o con dos parámetros […] o de alguna otra manera definida por la implementación.
¿”Alguna otra manera definida por la implementación” incluye una matriz global? (Me parece que la especificación todavía se refiere a un función.)
Si no, ¿es una extensión del compilador? ¿O una característica de las cadenas de herramientas, que sirve para algún otro propósito y decidieron ponerla a disposición a través de la interfaz?
Rey del cielo
Es porque C permite un entorno “no alojado” o independiente que no requiere la main
función. Esto significa que el nombre main
queda libre para otros usos. Es por esto que el lenguaje como tal permite tales declaraciones. La mayoría de los compiladores están diseñados para admitir ambos (la diferencia es principalmente cómo se realiza la vinculación) y, por lo tanto, no rechazan construcciones que serían ilegales en un entorno alojado.
La sección a la que hace referencia en el estándar se refiere al entorno alojado, la correspondiente para independiente es:
en un entorno independiente (en el que la ejecución del programa C puede tener lugar sin ningún beneficio de un sistema operativo), el nombre y el tipo de la función llamada al inicio del programa están definidos por la implementación. Todas las instalaciones de biblioteca disponibles para un programa independiente, que no sean el conjunto mínimo requerido por la cláusula 4, están definidas por la implementación.
Si luego lo vincula como de costumbre, saldrá mal ya que el vinculador normalmente tiene poco conocimiento sobre la naturaleza de los símbolos (qué tipo tiene o incluso si es una función o variable). En este caso, el enlazador resolverá gustosamente las llamadas a main
a la variable nombrada main
. Si no se encuentra el símbolo, se producirá un error de enlace.
Si lo está vinculando como de costumbre, básicamente está tratando de usar el compilador en una operación alojada y luego no define main
como se supone que significa un comportamiento indefinido según el apéndice J.2:
el comportamiento es indefinido en las siguientes circunstancias:
- …
- El programa en un entorno alojado no define una función llamada principal usando una de las formas especificadas (5.1.2.2.1)
El propósito de la posibilidad independiente es poder usar C en entornos donde (por ejemplo) no se proporcionan bibliotecas estándar o inicialización CRT. Esto significa que el código que se ejecuta antes main
se llama (esa es la inicialización de CRT que inicializa el tiempo de ejecución de C) podría no proporcionarse y se esperaría que lo proporcione usted mismo (y puede decidir tener un main
o puede decidir no hacerlo).
-
Esto compila y vincula bien (bueno, con una advertencia) con gcc 4.9.3 en cygwin:
int f(int argc,char **argv) { return 0; } char *main = (char *)f;
– Peter – Reincorporar a Mónica
13 de enero de 2016 a las 11:05
-
@PeterA.Schneider Pero si funciona bien, es pura suerte. El CRT-init intentará llamar
main
que es donde se almacena el puntero y no a lo que apunta.– Rey del cielo
13 de enero de 2016 a las 11:11
-
Se enlaza pero segfaults. Por cierto, no creo que la pregunta tenga mucho que ver con “independiente”. Por ejemplo, las siguientes compilaciones y enlaces (a un dll) en VS13:
namespace Main_abused { class Program { int Main = 0; } }
. Es más bien que main (y Main en C#) no son palabras clave, y los enlazadores de C son tontos, err, simples.– Peter – Reincorporar a Mónica
13 de enero de 2016 a las 11:14
-
@PeterA.Schneider No estoy de acuerdo, un programa con
main
no estar definido de una manera diferente a lo que requiere el estándar (o la implementación especificada) está mal formado.– Rey del cielo
13 de enero de 2016 a las 11:25
-
Esto no es realmente exacto. La sección alojada de C99/C11 tiene una oración retrasada “o de alguna otra manera definida por la implementación”, que no está del todo clara. Así que nadie sabe realmente qué formas de main están permitidas… Discutido en detalle aquí.
– Lundin
13 de mayo de 2016 a las 9:26
tymmej
Si está interesado en cómo crear un programa en la matriz principal: https://jroweboy.github.io/c/asm/2015/01/26/when-is-main-not-a-function.html. La fuente de ejemplo allí solo contiene una matriz char (y luego int) llamada main
que está lleno de instrucciones de máquina.
Los principales pasos y problemas fueron:
- Obtenga las instrucciones de la máquina de una función principal de un volcado de memoria gdb y cópielo en la matriz
- Etiquetar los datos en
main[]
ejecutable declarándolo const (aparentemente, los datos son escribibles o ejecutables) - Último detalle: cambiar una dirección para datos de cadena reales.
El código C resultante es simplemente
const int main[] = {
-443987883, 440, 113408, -1922629632,
4149, 899584, 84869120, 15544,
266023168, 1818576901, 1461743468, 1684828783,
-1017312735
};
pero da como resultado un programa ejecutable en una PC de 64 bits:
$ gcc -Wall final_array.c -o sixth
final_array.c:1:11: warning: ‘main’ is usually a function [-Wmain]
const int main[] = {
^
$ ./sixth
Hello World!
Lundin
El problema es ese main
no es un identificador reservado. El estándar C solo dice que en los sistemas alojados suele haber una función llamada main. Pero nada en el estándar le impide abusar del mismo identificador para otros fines siniestros.
GCC le da una advertencia engreída “principal suele ser una función”, insinuando que el uso del identificador main
para otros fines no relacionados no es una idea brillante.
Ejemplo tonto:
#include <stdio.h>
int main (void)
{
int main = 5;
main:
printf("%d\n", main);
main--;
if(main)
{
goto main;
}
else
{
int main (void);
main();
}
}
Este programa imprimirá repetidamente los números 5,4,3,2,1 hasta que se desborde la pila y se cuelgue (no intente esto en casa). Desafortunadamente, el programa anterior es un programa C estrictamente conforme y el compilador no puede evitar que lo escribas.
main
es, después de compilar, solo otro símbolo en un archivo de objeto como muchos otros (funciones globales, variables globales, etc.).
El enlazador vinculará el símbolo. main
independientemente de su tipo. De hecho, el enlazador no puede ver el tipo del símbolo en absoluto (él pueden mira, que no está en el .text
-sección sin embargo, pero a él no le importa;))
Usando gcc, el punto de entrada estándar es _start, que a su vez llama a main() después de preparar el entorno de tiempo de ejecución. Por lo tanto, saltará a la dirección de la matriz de enteros, lo que generalmente dará como resultado una instrucción incorrecta, una falla de segmento o algún otro comportamiento incorrecto.
Todo esto, por supuesto, no tiene nada que ver con el estándar C.
Solo compila porque no usa las opciones adecuadas (y funciona porque los enlazadores a veces solo se preocupan por el nombres de símbolos, no sus escribe).
$ gcc -std=c89 -pedantic -Wall x.c
x.c:1:5: warning: ISO C forbids zero-size array ‘main’ [-Wpedantic]
int main[0];
^
x.c:1:5: warning: ‘main’ is usually a function [-Wmain]
-
Todavía compila y enlaza. La única diferencia es que te avisa de eso.
main
suele ser una función (luego continúa y enlaza de todos modos).– Rey del cielo
13 de enero de 2016 a las 11:14
-
@skyking ¿Quieres que la compilación/enlace falle? Agregar
-Werror
después.– Jens
13 de enero de 2016 a las 12:22
-
Pero entonces (otros) programas C válidos tampoco se compilarían.
– Rey del cielo
13 de enero de 2016 a las 12:33
-
@skyking Luego agrega
-Wno-*
por las advertencias que eligió aceptar. La mayoría de las veces, las advertencias son fáciles de corregir y, si no lo son, algo está mal con el código, IMNSHO. yo suelo-Werror
desde hace años y se ha demostrado su valor. Las nuevas advertencias son imposibles de perder y deben corregirse para continuar.– Jens
13 de enero de 2016 a las 12:45
-
Estoy de acuerdo que
-Werror
y habilitar las advertencias es una buena idea, pero eso no contradice el hecho de que hacerlo hará que el compilador no pueda compilar programas C válidos.– Rey del cielo
13 de enero de 2016 a las 12:46
zibrí
const int main[1] = { 0xc3c3c3c3 };
Esto compila y se ejecuta en x86_64… no hace nada, solo regresa: D
-
Todavía compila y enlaza. La única diferencia es que te avisa de eso.
main
suele ser una función (luego continúa y enlaza de todos modos).– Rey del cielo
13 de enero de 2016 a las 11:14
-
@skyking ¿Quieres que la compilación/enlace falle? Agregar
-Werror
después.– Jens
13 de enero de 2016 a las 12:22
-
Pero entonces (otros) programas C válidos tampoco se compilarían.
– Rey del cielo
13 de enero de 2016 a las 12:33
-
@skyking Luego agrega
-Wno-*
por las advertencias que eligió aceptar. La mayoría de las veces, las advertencias son fáciles de corregir y, si no lo son, algo está mal con el código, IMNSHO. yo suelo-Werror
desde hace años y se ha demostrado su valor. Las nuevas advertencias son imposibles de perder y deben corregirse para continuar.– Jens
13 de enero de 2016 a las 12:45
-
Estoy de acuerdo que
-Werror
y habilitar las advertencias es una buena idea, pero eso no contradice el hecho de que hacerlo hará que el compilador no pueda compilar programas C válidos.– Rey del cielo
13 de enero de 2016 a las 12:46
Eso no compilar. ISO C prohíbe matrices de tamaño cero.
– Jens
13/01/2016 a las 11:00
No está permitido por la especificación C. Los compiladores a menudo implementan cosas que no están cubiertas por la especificación.
–MM
13 de enero de 2016 a las 11:06
Pregunta relacionada: ¿Cómo puede funcionar un programa con una variable global llamada main en lugar de una función main?. Creo que también inspirado en una pregunta de codegolf.
– Shafik Yaghmour
06/10/2016 a las 20:21
@MM Especialmente en el caso de Malbolge
– Vía Láctea90
21 de junio de 2019 a las 21:56