used - static variable in c
¿Por qué declara main como una compilación de matriz? (6)
El problema es que
main
no es un identificador reservado.
El estándar C solo dice que en los sistemas alojados generalmente hay 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 petulante "main es generalmente una función", insinuando que el uso del identificador
main
para otros propósitos 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 bloquee (no intente esto en casa). Desafortunadamente, el programa anterior es un programa C estrictamente conforme y el compilador no puede evitar que lo escriba.
Vi
un fragmento de código en CodeGolf
que pretende ser una bomba compiladora, 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: ''main'' 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 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 una función ).
Si no, ¿es una extensión del compilador? ¿O una característica de las cadenas de herramientas, que tiene algún otro propósito y decidieron ponerla a disposición a través de la interfaz?
Es porque C permite un entorno "no alojado" o independiente que no requiere la función
main
.
Esto significa que el nombre
main
se libera para otros usos.
Es por eso 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 el entorno alojado.
La sección a la que se refiere en el estándar se refiere al entorno alojado, el 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. Cualquier instalación de biblioteca disponible para un programa independiente, que no sea el conjunto mínimo requerido por la cláusula 4, está definida por la implementación.
Si luego lo vincula como de costumbre, se volverá malo 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á felizmente las llamadas a
main
a la variable llamada
main
.
Si no se encuentra el símbolo, dará como resultado un error de enlace.
Si lo está vinculando como de costumbre, básicamente está tratando de usar el compilador en la 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 no está definido en las siguientes circunstancias:
- ...
- El programa en un entorno alojado no define una función llamada main utilizando uno de los formularios especificados (5.1.2.2.1)
El propósito de la posibilidad independiente es poder usar C en entornos donde (por ejemplo) bibliotecas estándar o inicialización CRT no se proporciona.
Esto significa que el código que se ejecuta antes de que se llame a
main
(esa es la inicialización de CRT que inicializa el tiempo de ejecución de C) podría no proporcionarse y se espera que lo proporcione usted mismo (y puede decidir tener un
main
o decidir no hacerlo) .
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 se llena con instrucciones de la máquina.
Los principales pasos y problemas fueron:
- Obtenga las instrucciones de máquina de una función principal de un volcado de memoria gdb y cópielo en la matriz
-
Etiquete los datos en el ejecutable
main[]
declarándolo constante (los datos aparentemente pueden escribirse o ejecutarse) - Último detalle: cambie una dirección para datos de cadena reales.
El código C resultante es solo
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!
Solo se compila porque no usa las opciones adecuadas (y funciona porque los enlazadores a veces solo se preocupan por los nombres de los símbolos, no por su tipo ).
$ 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]
main
es, después de compilar, solo otro símbolo en un archivo de objeto como muchos otros (funciones globales, variables globales, etc.).
El vinculador vinculará el símbolo
main
independientemente de su tipo.
De hecho, el enlazador no puede ver el tipo de símbolo (
puede
ver que no está en la
.text
.text, pero no le importa).
Con gcc, el punto de entrada estándar es _start, que a su vez llama a main () después de preparar el entorno de ejecución. Por lo tanto, saltará a la dirección de la matriz de enteros, lo que generalmente dará como resultado una mala instrucción, un defecto o algún otro mal comportamiento.
Todo esto, por supuesto, no tiene nada que ver con el estándar C.
const int main[1] = { 0xc3c3c3c3 };
Esto compila y se ejecuta en x86_64 ... no hace nada solo devuelve: D