¿Por qué se compila la declaración de main como una matriz?


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 fine 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á permitido por la especificación C? La sección que I piensa 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 forma 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 de compilador? ¿O una característica de las cadenas de herramientas, que sirve para algún otro propósito y decidieron hacerlo disponible a través del frontend?

Author: Community, 2016-01-13

6 answers

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. Esta es la razón por la que el lenguaje como tal permite tales declaraciones. La mayoría de los compiladores están diseñados para soportar ambos (la diferencia es principalmente cómo se hace el enlace) 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 el beneficio de un sistema operativo), el nombre y el tipo de la función llamada en el programa el inicio está definido por la implementación. Cualquier biblioteca disponible para un independiente los programas, aparte del conjunto mínimo requerido por la cláusula 4, están definidos por la implementación.

Si luego lo enlazas como de costumbre, saldrá mal ya que el enlazador 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 el símbolo no se encuentra, se producirá un error de enlace.

Si lo vinculas como de costumbre, básicamente estás tratando de usar el compilador en operaciones alojadas y luego no definir main como se supone que significa un comportamiento indefinido según el apéndice J. 2:

El el comportamiento es indefinido en las siguientes circunstancias:

  • ...
  • el programa en un entorno alojado no define una función llamada principal usando uno 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 da la inicialización de bibliotecas estándar o CRT. Esto significa que se llama al código que se ejecuta antes de main (esa es la inicialización CRT que inicializa el tiempo de ejecución de C) puede que no se proporcione y se esperaría que usted mismo lo proporcione (y puede decidir tener un main o puede decidir no hacerlo).

 34
Author: skyking,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-13 11:29:22

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 más tarde int) llamada main que está llena de instrucciones de 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 el array
  • Etiqueta los datos en main[] ejecutable por declarándolo const (los datos son aparentemente escribibles o ejecutables)
  • Último detalle: Cambie una dirección para los 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 resulta en un programa ejecutable en un 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!
 23
Author: tymmej,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-13 11:41:07

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 enlazará el símbolo main independientemente de su tipo. De hecho, el enlazador no puede ver el tipo del símbolo en absoluto (él puede ver, que no está en la .text-sección sin embargo, pero 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. Así será saltar a la dirección de la matriz entera, que por lo general dará lugar a una mala instrucción, falla de segmento o algún otro mal comportamiento.

Todo esto, por supuesto, no tiene nada que ver con el estándar C.

 8
Author: Ctx,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-13 11:04:16

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 engreída "main es generalmente una función", dando a entender 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 imprima repetidamente los números 5,4,3,2,1 hasta que obtenga un desbordamiento de 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.

 7
Author: Lundin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-05-13 09:47:48

Solo compila porque no se usan 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]
 2
Author: Jens,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-01-13 11:04:34
const int main[1] = { 0xc3c3c3c3 };

Esto compila y ejecuta en x86_64... no hace nada solo regresar: D

 0
Author: Zibri,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-04-25 07:56:21