Es 'int main;' un programa válido de C / C ++?

113

Pregunto porque mi compilador parece pensar que sí, aunque yo no.

echo 'int main;' | cc -x c - -Wall
echo 'int main;' | c++ -x c++ - -Wall

Clang no emite advertencias ni errores con esto, y gcc solo emite la advertencia mansa:, 'main' is usually a function [-Wmain]pero solo cuando se compila como C. Especificar a -std=no parece importar.

De lo contrario, se compila y enlaza bien. Pero en la ejecución, termina inmediatamente con SIGBUS(para mí).

Leer las (excelentes) respuestas en ¿Qué debería devolver main () en C y C ++? y un rápido grep a través de las especificaciones del idioma, ciertamente me parecería que se requiere una función principal . Pero la verborrea de gcc -Wmain('main' suele ser una función) (y la escasez de errores aquí) parece sugerir lo contrario.

¿Pero por qué? ¿Hay algún uso extraño o “histórico” para esto? Alguien sabe lo que da?

Mi punto, supongo, es que realmente creo que esto debería ser un error en un entorno alojado, ¿eh?

Geoff Nixon
fuente
6
Para hacer de gcc un compilador (en su mayoría) compatible con el estándar, necesitagcc -std=c99 -pedantic ...
pmg
3
@pmg Es la misma advertencia, con o sin -pedantico cualquiera -std. Mi sistema c99también compila esto sin advertencia ni error ...
Geoff Nixon
3
Desafortunadamente, si es "lo suficientemente inteligente", puede crear cosas que sean aceptables para el compilador pero que no tengan sentido. En este caso, está vinculando la biblioteca de tiempo de ejecución de C para llamar a una variable llamada main, que es poco probable que funcione. Si inicializa main con el valor "correcto", en realidad puede devolver ...
Mats Petersson
7
E incluso si es válido, es algo terrible (código ilegible). Por cierto, podría ser diferente en implementaciones alojadas y en implementaciones independientes (que no conozco main)
Basile Starynkevitch
1
Para más momentos divertidos, pruebemain=195;
imallett

Respuestas:

97

Dado que la pregunta tiene doble etiqueta como C y C ++, el razonamiento para C ++ y C sería diferente:

  • C ++ usa la alteración de nombres para ayudar al enlazador a distinguir entre símbolos textualmente idénticos de diferentes tipos, por ejemplo, una variable global xyzy una función global independiente xyz(int). Sin embargo, el nombre mainnunca se estropea.
  • C no usa alteración, por lo que es posible que un programa confunda al enlazador al proporcionar un símbolo de un tipo en lugar de un símbolo diferente y hacer que el programa se vincule correctamente.

Eso es lo que está sucediendo aquí: el enlazador espera encontrar el símbolo main , y lo hace. "Conecta" ese símbolo como si fuera una función, porque no conoce nada mejor. La parte de la biblioteca en tiempo de ejecución que pasa el control a la que mainsolicita el vinculador main, por lo que el vinculador le da un símbolo main, dejando que la fase de vínculo se complete. Por supuesto, esto falla en tiempo de ejecución, porque mainno es una función.

Aquí hay otra ilustración del mismo problema:

archivo xc:

#include <stdio.h>
int foo(); // <<== main() expects this
int main(){
    printf("%p\n", (void*)&foo);
    return 0;
}

archivo yc:

int foo; // <<== external definition supplies a symbol of a wrong kind

compilando:

gcc x.c y.c

Esto se compila, y probablemente se ejecutaría, pero es un comportamiento indefinido, porque el tipo de símbolo prometido al compilador es diferente del símbolo real proporcionado al enlazador.

En lo que respecta a la advertencia, creo que es razonable: C le permite crear bibliotecas que no tienen mainfunción, por lo que el compilador libera el nombre mainpara otros usos si necesita definir una variable mainpor alguna razón desconocida.

dasblinkenlight
fuente
3
Sin embargo, el compilador de C ++ trata la función principal de manera diferente. Su nombre no está mutilado incluso sin la "C" externa. Supongo que es porque de lo contrario necesitaría emitir su propio main externo "C", para asegurar la vinculación.
UldisK
@UldisK Sí, lo noté yo mismo y lo encontré bastante interesante. Tiene sentido, pero nunca había pensado en eso.
Geoff Nixon
2
En realidad, los resultados para C ++ y C no son diferentes, como se señala aquí, mainno está sujeto a alteración de nombres (eso parece) en C ++, sea o no una función.
Geoff Nixon
4
@nm Creo que su interpretación de la pregunta es demasiado estrecha: además de hacer la pregunta en el título de la publicación, OP claramente busca una explicación de por qué su programa se compiló en primer lugar ("mi compilador parece pensar que sí, aunque no lo hago "), así como una sugerencia de por qué podría ser útil definir maincomo cualquier otra cosa que no sea una función. La respuesta ofrece una explicación para ambas partes.
dasblinkenlight
1
Que el símbolo principal no esté sujeto a alteración de nombres es irrelevante. No se menciona la alteración de nombres en el estándar C ++. La alteración de nombres es un problema de implementación.
David Hammen
30

mainno es una palabra reservada que es sólo un identificador predefinido (como cin, endl, npos...), por lo que podría declarar una variable llamada main, inicializar y luego imprimir su valor.

Por supuesto:

  • la advertencia es útil ya que es bastante propenso a errores;
  • puede tener un archivo fuente sin la main()función (bibliotecas).

EDITAR

Algunas referencias:

  • main no es una palabra reservada (C ++ 11):

    La función mainno se debe utilizar dentro de un programa. El vínculo (3.5) de mainestá definido por la implementación. Un programa que define principal como eliminado o que declara principal sea inline, statico constexprse forma mal. El nombre mainno está reservado de otra manera. [Ejemplo: se pueden llamar funciones miembro, clases y enumeraciones main, al igual que entidades en otros espacios de nombres. - ejemplo final]

    C ++ 11 - [basic.start.main] 3.6.1.3

    [2.11 / 3] [...] algunos identificadores están reservados para su uso por implementaciones de C ++ y bibliotecas estándar (17.6.4.3.2) y no deben usarse de otro modo; no se requiere diagnóstico.

    [17.6.4.3.2 / 1] Ciertos conjuntos de nombres y firmas de funciones siempre están reservados para la implementación:

    • Cada nombre que contenga un subrayado doble __ o comience con un subrayado seguido de una letra mayúscula (2.12) está reservado para la implementación para cualquier uso.
    • Cada nombre que comienza con un guión bajo se reserva para la implementación para su uso como nombre en el espacio de nombres global.
  • Palabras reservadas en lenguajes de programación .

    Es posible que el programador no vuelva a definir las palabras reservadas, pero las predefinidas a menudo se pueden anular de alguna manera. Este es el caso de main: hay ámbitos en los que una declaración que utiliza ese identificador redefine su significado.

manlio
fuente
- Supongo que estoy bastante seducido por el hecho de que (ya que es tan propenso a errores), por qué esto es una advertencia (no un error), y por qué es solo una advertencia cuando se compila como C - Claro, puede compilar sin una main()función, pero no puede vincularla como un programa. Lo que sucede aquí es que un programa "válido" se vincula sin un main(), solo un main.
Geoff Nixon
7
ciny endlno están en el espacio de nombres predeterminado, están en el stdespacio de nombres. nposes miembro de std::basic_string.
AnotherParker
1
main está reservado como nombre global. Ninguna de las otras cosas que mencionaste, ni mainestán predefinidas.
Potatoswatter
1
Consulte C ++ 14 §3.6.1 y C11 §5.1.2.2.1 para conocer las limitaciones sobre lo que mainse permite. C ++ dice "Una implementación no predefinirá la función principal" y C dice "La implementación no declara ningún prototipo para esta función".
Potatoswatter
@manlio: aclare de qué está citando. En cuanto a C simple, las citas están equivocadas. Así que supongo que es alguno de los estándares de C ++, ¿no?
dhein
19

¿Es int main;un programa C / C ++ válido?

No está del todo claro qué es un programa C / C ++.

¿Es int main;un programa C válido?

Si. Se permite una implementación independiente para aceptar dicho programa. mainno tiene por qué tener ningún significado especial en un entorno independiente.

Es no válido en un entorno alojado.

¿Es int main;un programa C ++ válido?

Ídem.

¿Por qué choca?

El programa no tiene por qué tener sentido en su entorno. En un entorno independiente, el inicio y la terminación del programa, y ​​el significado de main, están definidos por la implementación.

¿Por qué me advierte el compilador?

El compilador puede advertirle sobre lo que le plazca, siempre que no rechace los programas conformes. Por otro lado, la advertencia es todo lo que se requiere para diagnosticar un programa no conforme. Dado que esta unidad de traducción no puede formar parte de un programa alojado válido, se justifica un mensaje de diagnóstico.

¿Es gccun entorno independiente o es un entorno alojado?

Si.

gccdocumenta la -ffreestandingbandera de compilación. Agréguelo y la advertencia desaparecerá. Es posible que desee utilizarlo al compilar, por ejemplo, kernels o firmware.

g++no documenta tal bandera. Suministrarlo parece no tener ningún efecto en este programa. Probablemente sea seguro asumir que el entorno proporcionado por g ++ está alojado. La ausencia de diagnóstico en este caso es un error.

norte. 'pronombres' m.
fuente
17

Es una advertencia ya que técnicamente no está prohibida. El código de inicio usará la ubicación del símbolo de "main" y saltará a él con los tres argumentos estándar (argc, argv y envp). No lo hace, y en el momento del enlace no puede verificar que en realidad sea una función, ni siquiera que tenga esos argumentos. Esta es también la razón por la que int main (int argc, char ** argv) funciona: el compilador no conoce el argumento envp y resulta que no se usa, y es una limpieza de llamadas.

Como broma, podrías hacer algo como

int main = 0xCBCBCBCB;

en una máquina x86 e, ignorando las advertencias y cosas similares, no solo se compilará, sino que también funcionará.

Alguien usó una técnica similar a esta para escribir un ejecutable (más o menos) que se ejecuta directamente en varias arquitecturas: http://phrack.org/issues/57/17.html#article . También se utilizó para ganar el IOCCC - http://www.ioccc.org/1984/mullender/mullender.c .

dascandy
fuente
1
"Es una advertencia, ya que técnicamente no está prohibido", no es válido en C ++.
Saludos y hth. - Alf
3
"los tres argumentos estándar (argc, argv y envp)": aquí posiblemente se esté hablando del estándar Posix.
Saludos y hth. - Alf
En mi sistema (Ubuntu 14 / x64), la siguiente línea funciona con gcc:int main __attribute__ ((section (".text")))= 0xC3C3C3C3;
csharpfolk
@ Cheersandhth.-Alf Los dos primeros son estándar, el tercero es POSIX.
dascandy
9

¿Es un programa válido?

No.

No es un programa ya que no tiene partes ejecutables.

¿Es válido compilar?

Si.

¿Se puede utilizar con un programa válido?

Si.

No se requiere que todo el código compilado sea ejecutable para ser válido. Los ejemplos son bibliotecas estáticas y dinámicas.

Ha creado efectivamente un archivo de objeto. No es un ejecutable válido, sin embargo, otro programa podría vincularse al objeto mainen el archivo resultante cargándolo en tiempo de ejecución.

¿Debería ser un error?

Tradicionalmente, C ++ permite al usuario hacer cosas que pueden parecer que no tienen un uso válido pero que encajan con la sintaxis del lenguaje.

Quiero decir que seguro, esto podría reclasificarse como un error, pero ¿por qué? ¿Para qué serviría eso que la advertencia no?

Siempre que exista una posibilidad teórica de que esta funcionalidad se utilice en el código real, es muy poco probable que al llamar a un objeto que no sea una función se produzca mainun error de acuerdo con el lenguaje.

Michael Gazonda
fuente
Crea un símbolo visible externamente llamado main. ¿Cómo puede un programa válido, que debe tener una función visible externamente nombrada main, vincularse a él?
Keith Thompson
@KeithThompson Carga en tiempo de ejecución. Aclarará.
Michael Gazonda
Puede porque no puede diferenciar entre tipos de símbolos. La vinculación funciona bien, la ejecución (excepto en el caso cuidadosamente elaborado) no.
Chris Stratton
1
@ChrisStratton: Creo que el argumento de Keith es que la vinculación falla porque el símbolo está definido de forma múltiple ... porque el "programa válido" no sería un programa válido a menos que defina una mainfunción.
Ben Voigt
@BenVoigt Pero si aparece en una biblioteca, entonces la vinculación no fallará (y probablemente no), porque en el momento de la vinculación del programa, la int main;definición no será visible.
6

Me gustaría agregar a las respuestas ya dadas citando los estándares reales del idioma.

Es 'int main;' un programa C válido?

Respuesta corta (mi opinión): solo si su implementación utiliza un "entorno de ejecución independiente".

Todas las citas siguientes de C11

5. Medio ambiente

Una implementación traduce archivos fuente C y ejecuta programas C en dos entornos de sistema de procesamiento de datos, que se denominarán entorno de traducción y entorno de ejecución [...]

5.1.2 Entornos de ejecución

Se definen dos entornos de ejecución: autónomo y alojado. En ambos casos, el inicio del programa se produce cuando el entorno de ejecución llama a una función C designada.

5.1.2.1 Entorno independiente

En un entorno independiente (en el que la ejecución del programa en 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.

5.1.2.2 Entorno alojado

No es necesario proporcionar un entorno alojado, pero deberá cumplir con las siguientes especificaciones si están presentes.

5.1.2.2.1 Inicio del programa

La función llamada al inicio del programa se denomina main . [...] Se definirá con un tipo de retorno de int y sin parámetros [...] o con dos parámetros [...] o equivalentes o de alguna otra manera definida por la implementación.

De estos, se observa lo siguiente:

  • Un programa C11 puede tener un entorno de ejecución independiente o alojado y ser válido.
  • Si tiene uno independiente, no es necesario que exista una función principal.
  • De lo contrario, debe haber uno con un valor de retorno de tipo int .

En un entorno de ejecución independiente, yo diría que es un programa válido que no permite que suceda el inicio, porque no hay una función presente para eso como se requiere en 5.1.2. En un entorno de ejecución alojado, mientras su código introduce un objeto llamado main , no puede proporcionar un valor de retorno, por lo que yo diría que no es un programa válido en este sentido, aunque también se podría argumentar como antes que si el programa no es está destinado a ser ejecutado (es posible que on desee proporcionar datos solo, por ejemplo), entonces simplemente no permite hacer eso.

Es 'int main;' un programa C ++ válido?

Respuesta corta (mi opinión): solo si su implementación utiliza un "entorno de ejecución independiente".

Cita de C ++ 14

3.6.1 Función principal

Un programa debe contener una función global llamada main, que es el inicio designado del programa. Se define por implementación si se requiere un programa en un entorno independiente para definir una función principal. [...] Tendrá un tipo de retorno de tipo int, pero de lo contrario su tipo está definido por la implementación. [...] El nombre principal no está reservado de otra manera.

Aquí, a diferencia del estándar C11, se aplican menos restricciones al entorno de ejecución independiente, ya que no se menciona ninguna función de inicio, mientras que para un entorno de ejecución alojado, el caso es prácticamente el mismo que para C11.

Nuevamente, diría que para el caso alojado, su código no es un programa C ++ 14 válido, pero estoy seguro de que es para el caso independiente.

Dado que mi respuesta solo considera el entorno de ejecución , creo que la respuesta de dasblinkenlicht entra en juego, ya que la alteración de nombres que se produce en el entorno de traducción ocurre de antemano. Aquí, no estoy tan seguro de que las citas anteriores se cumplan de manera tan estricta.

Ingo Schalk-Schupp
fuente
4

Mi punto, supongo, es que realmente creo que esto debería ser un error en un entorno alojado, ¿eh?

El error es tuyo. No especificó una función llamada mainque devuelve un inte intentó utilizar su programa en un entorno alojado.

Suponga que tiene una unidad de compilación que define una variable global llamada main. Esto bien podría ser legal en un entorno independiente porque lo que constituye un programa se deja a la implementación en entornos independientes.

Suponga que tiene otra unidad de compilación que define una función global llamada mainque devuelve an inty no toma argumentos. Esto es exactamente lo que necesita un programa en un entorno alojado.

Todo está bien si solo usa la primera unidad de compilación en un entorno independiente y solo usa la segunda en un entorno alojado. ¿Qué pasa si usa ambos en un programa? En C ++, ha violado la regla de una definición. Ese es un comportamiento indefinido. En C, ha violado la regla que dicta que todas las referencias a un solo símbolo deben ser coherentes; si no es así, es un comportamiento indefinido. El comportamiento indefinido es un "¡sal de la cárcel, gratis!" tarjeta a los desarrolladores de una implementación. Todo lo que haga una implementación en respuesta a un comportamiento indefinido cumple con el estándar. La implementación no tiene que advertir, y mucho menos detectar, un comportamiento indefinido.

¿Qué pasa si usa solo una de esas unidades de compilación, pero usa la incorrecta (que es lo que hizo)? En C, la situación está clara. No definir la función mainen una de las dos formas estándar en un entorno alojado es un comportamiento indefinido. Suponga que no definió mainen absoluto. El compilador / enlazador no tiene nada que decir sobre este error. Que se quejen es una delicadeza en su nombre. Que el programa C compilado y vinculado sin errores es culpa suya, no del compilador.

Es un poco menos claro en C ++ porque no definir la función mainen un entorno alojado es un error en lugar de un comportamiento indefinido (en otras palabras, debe ser diagnosticado). Sin embargo, la regla de una definición en C ++ significa que los enlazadores pueden ser bastante tontos. El trabajo del vinculador es resolver referencias externas y, gracias a la regla de una definición, el vinculador no tiene que saber qué significan esos símbolos. Usted proporcionó un símbolo con nombre main, el vinculador espera ver un símbolo con nombre main, por lo que todo está bien en lo que respecta al vinculador.

David Hammen
fuente
4

Para C, hasta ahora es un comportamiento definido por la implementación.

Como dice la ISO / IEC9899:

5.1.2.2.1 Inicio del programa

1 La función llamada al inicio del programa se denomina 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:

int main(void) { /* ... */ }

o con dos parámetros (a los que se hace referencia aquí como argc y argv, aunque se pueden usar cualquier nombre, ya que son locales a la función en la que se declaran):

int main(int argc, char *argv[]) { /* ... */ }

o equivalente; o de alguna otra manera definida por la implementación.

dhein
fuente
3

No, este no es un programa válido.

Para C ++, esto se hizo recientemente explícitamente mal formado por el informe de defectos 1886: Enlace de idioma para main () que dice:

No parece haber ninguna restricción para otorgar a main () un enlace de lenguaje explícito, pero probablemente debería estar mal formado o admitido condicionalmente.

y parte de la resolución incluyó el siguiente cambio:

Un programa que declara una variable main en el ámbito global o que declara el nombre main con enlace en lenguaje C (en cualquier espacio de nombres) está mal formado.

Podemos encontrar esta redacción en el último borrador de estándar de C ++ N4527, que es el borrador de C ++ 1z.

Las últimas versiones de clang y gcc ahora hacen que esto sea un error ( véalo en vivo ):

error: main cannot be declared as global variable
int main;
^

Antes de este informe de defectos, era un comportamiento indefinido que no requiere un diagnóstico. Por otro lado, el código mal formado requiere un diagnóstico, el compilador puede convertir esto en una advertencia o un error.

Shafik Yaghmour
fuente
¡Gracias por la actualización! Es bueno ver que esto ahora se detecta con los diagnósticos del compilador. Sin embargo, debo decir que encuentro los cambios en el estándar C ++ desconcertantes. (Para conocer los antecedentes, consulte los comentarios anteriores sobre la modificación de nombres de main()). Entiendo la razón para no permitir main()tener una especificación de enlace explícita, pero no entiendo que exija que main()tenga un enlace C ++ . Por supuesto, la norma no lo hace directamente dirección de cómo manejar ABI vinculación / renombrado de nombres, pero en la práctica (por ejemplo, con Itanium ABI) esto sería destrozar main()a _Z4mainv. ¿Qué me estoy perdiendo?
Geoff Nixon
Creo que el comentario de Supercat cubre eso. Si la implementación está haciendo lo suyo antes de llamar al main definido por el usuario, entonces podría elegir fácilmente llamar a un nombre mutilado.
Shafik Yaghmour