2012-04-29 7 views
5

podemos escribir la función principal de varias maneras,Cómo CRT llama principal, que tienen diferente parámetro

  1. int main()
  2. int main(int argc,char *argv[])
  3. int main(int argc,char *argv[],char * environment)

Cómo tiempo de ejecución función CRT sabe que principales debería ser llamado. Tenga en cuenta que no estoy preguntando si Unicode es compatible o no.

Respuesta

5

La respuesta aceptada es incorrecta, no hay un código especial en el CRT para reconocer el tipo de declaración main().

Funciona debido a la convención de llamadas cdecl. Lo que especifica que los argumentos se empujan en la pila de derecha a izquierda y que la llamada limpia la pila después de la llamada. Entonces el CRT simplemente pasa todos los argumentos a main() y los vuelve a mostrar cuando main() retorna. Lo único que debe hacer es especificar los argumentos en el orden correcto en su declaración de función main(). El parámetro argc tiene que ser el primero, es el que se encuentra en la parte superior de la pila. argv tiene que ser el segundo, etcétera. Omitir un argumento no hace diferencia, siempre y cuando también omitas todos los que siguen.

Esta es también la razón por la cual la función printf() puede funcionar, tiene un número variable de argumentos. Con un argumento en una posición conocida, el primero.

+0

Probablemente sean tiempos de ejecución de Windows C precisos de 32 bits. Pero, ¿qué pasa en otras plataformas donde difiere ABI? Por ejemplo, x64 donde los parámetros se pasan en los registros incluso para cdecl. ¿Y qué tal una función 'main' conforme declarada así' int main (double d) '? ¿Y dónde en el estándar dice que todas las funciones 'main' deben usar' __cdecl' que ni siquiera forma parte del estándar? ¿Y qué hay de 'WinMain'? –

+0

Bueno, al menos la primera frase de su respuesta ahora es precisa. ;-) –

+0

Sour grapes David? Su respuesta puede ser correcta, simplemente no conozco ningún compilador que soporte el tipo de reflexión que se requeriría para que funcione. Solo puedo ofrecer lo que he visto usado en 8 de ellos que estudié lo suficiente, todos usaron una convención de llamadas de cdecl. Probablemente porque es muy fácil de implementar, aunque no es tan fácil generar el código. La reflexión es una característica muy poco común en C. ¿Quizás puedas hacerlo más convincente dando un ejemplo de dicho compilador? –

4

En general, el compilador/enlazador necesitaría reconocer la forma particular de main que está utilizando y luego incluir el código para adaptarlo desde la función de inicio del sistema a su función C o C++ main.

Es cierto que compiladores específicos en plataformas específicas podrían escaparse sin hacer esto, utilizando los métodos que Hans describe en su respuesta. Sin embargo, no todas las plataformas usan la pila para pasar parámetros, y es posible escribir implementaciones C y C++ conformes que tienen listas de parámetros incompatibles. Para tales casos, entonces el compilador/enlazador necesitaría determinar qué forma de main llamar.

+0

Estoy de acuerdo, pero cómo reconoce y genera el código, incluso si lo tomamos como una sobrecarga, C no lo admite. –

+0

Gracias. Respuesta aceptada –

+0

Has encontrado este error, verifica mi respuesta. –

0

En primer lugar, la función main está tratada específicamente en GCC (por ejemplo, la main_identifier_node en el archivo gcc/c-family/c-common.c del árbol de fuentes de GCC 4.7)

Y el C11 y C++ 11 normas tienen una redacción específica y la especificación de ella.

Entonces, las convenciones de llamadas de C de AB suelen ser para que los argumentos adicionales no dañen demasiado.

Así que puede pensar que tanto la especificación del lenguaje como el compilador tienen cosas específicas con respecto a la "sobrecarga" de main.

Incluso creo que main podría no ser una función normal. Creo que algunas palabras en el estándar, que no tengo en este momento, podrían ser, por ejemplo, entendido como la prohibición de tomar su dirección o recursing en main.

En la práctica, main es llamado por algún código de ensamblado compilado en crt*.o archivos vinculados por gcc. Use gcc -v para comprender más lo que está sucediendo.

1

El C 99 Standard (5.1.2.2.1 Programa de inicio) dice que una implementación impone ninguna prototipo de la función main(), y que un programa puede definirlo como cualquiera de:

1) int main (vacío);

2) int main (int argc, char * argv []);

o de una manera semánticamente equivalente a 2), p.

2 ') int main (int argc, char ** argv);

o en otras formas de implementación definidas. Hace no mandato que el prototipo:

3) int main (int argc, char * argv [], char * envp []);

tendrá el comportamiento previsto, aunque ese prototipo debe compilarse, porque debe compilarse cualquier prototipo. 3) es compatible con GCC y Microsoft C, entre otros compiladores. (N.B. El tercer prototipo del cuestionario tiene char * envp en lugar de char * envp [], ya sea por accidente o porque tiene algún otro compilador).

Tanto GCC como Microsoft C compilarán main() con cualquier prototipo en absoluto, como deberían. Analizan el prototipo que realmente especifica y genera el lenguaje ensamblador para consumir los argumentos, si los hay, de la manera correcta. Así, por ejemplo, van a generar cada una el comportamiento esperado para el programa:

#include <stdio.h> 

void main(double d, char c) 
{ 
    printf("%lf\n",d); 
    putchar(c); 
} 

si se pudiera encontrar una manera de pasar un doble y un char directamente al programa, no a través de una matriz de cadenas.

Estas observaciones se pueden verificar habilitando los listados de lenguaje ensamblador para programas experimentales.

La cuestión de cómo el CRT estándar del compilador nos permite invocar la implementación generada de main() es distinta de la cuestión de cómo se puede definir main() para el compilador.

Tanto para GCC como para MS C, main() puede definirse de la manera que deseemos. En cada caso, sin embargo, el CRT estándar de la implementación, AFIK, admite pasar argumentos a main() solo que según 3). Entonces 1) - 2 ') también tendrá el comportamiento esperado al ignorar los argumentos en exceso, y no tenemos otras opciones para proporcionar un tiempo de ejecución no estándar propio.

La respuesta de Hans Passant parece incidentalmente engañosa al sugerir que argc le dice a la función cuántos argumentos subsiguientes consumir de la misma manera que el primer argumento para printf(). Si argc está presente, solo denota el número de elementos en la matriz pasada como el segundo argumento argv. No indica cuántos argumentos se pasan a main(). Tanto GCC como MS C averiguan cómo se esperan los argumentos al analizar el prototipo que usted escribe, básicamente lo que hace un compilador con cualquier función , excepto, como printf(), que están definidos para tomar una cantidad variable de argumentos.

main() no toma una cantidad variable de argumentos.Toma los argumentos que especifique en su definición, y los CRT estándar de los compiladores habituales asumen que son (int, char * [], char * []).

2

Hmmm. Parece que tal vez la respuesta actualmente aceptada, que indica que la respuesta previamente aceptada es incorrecta, es en sí misma incorrecta. Las etiquetas en esta pregunta indican que se aplica tanto a C++ como a C, por lo que me atengo a las especificaciones de C++, no a C99. Independientemente de todas las demás explicaciones o argumentos, la principal respuesta a esta pregunta es que "main() se trata de manera especial definida por la implementación." Creo que la respuesta de David es técnicamente más correcta que la de Hans, pero la explicaré en más detalle ...

La función main() es una divertida, tratada por el compilador & vinculador con un comportamiento que no coincide con ninguna otra función. Hans tiene razón en que no hay un código especial en el CRT para reconocer diferentes firmas de main(), pero su afirmación de que "funciona debido a la convención de llamadas cdecl" se aplica solo a plataforma (s) específica (s), notablemente Visual Studio. La verdadera razón de que no haya un código especial en el CRT para reconocer diferentes firmas de main() es que no es necesario. Y a pesar de que es una especie de divisor de pelos, es el enlazador cuyo trabajo es vincular el código de inicio en main() en tiempo de enlace, no es el trabajo del CRT en el momento del inicio.

Gran parte de cómo se trata la función main() está definida por la implementación, según las especificaciones de C++ (consulte la Sección 3.6, "Inicio y finalización"). Es probable que la mayoría de los compiladores implementen main() implícitamente con algo similar a la vinculación externa "C", dejando main() en un estado no decorado para que independientemente de su prototipo de función, su símbolo de enlazador sea el mismo. Alternativamente, el enlazador para una implementación podría ser lo suficientemente inteligente como para escanear a través de la tabla de símbolos buscando cualquiera cuyo nombre decorado resuelva a alguna forma de "[int | void] main (...)" (tenga en cuenta que el vacío como tipo de retorno es en sí mismo una cosa específica de la implementación, ya que la propia especificación dice que el tipo de retorno de main() debe ser 'int'). Una vez que dicha función se encuentra en los símbolos disponibles, el enlazador podría simplemente usar eso cuando el código de inicio se refiera a "main()", por lo que el nombre exacto del símbolo no necesariamente tiene que coincidir con nada en particular; incluso podría ser wmain() u otro, siempre que el vinculador sepa qué variaciones buscar, o el compilador dota todas las variaciones con el mismo nombre de símbolo.

También es importante tener en cuenta que la especificación dice que main() puede no estar sobrecargado, por lo que el enlazador no debería tener que "elegir" entre varias implementaciones de usuario de varias formas de main(). Si encuentra más de uno, se trata de un error de símbolo duplicado (u otro error similar) incluso si las listas de argumentos no coinciden. Y a pesar de todas las implementaciones “deberá” permitir que tanto

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

y

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

También se les autoriza para permitir que otras listas de argumentos, incluidos la versión demuestras que incluye un puntero de matriz de cadenas ambiente, y cualquier otra variación que tiene sentido en cualquier implementación dada. Como indica Hans, la convención de llamadas cdecl del compilador de Visual Studio (y convenciones de llamadas de muchos otros compiladores) proporciona un marco en el que un llamante puede configurar el entorno de llamadas (es decir, la pila o registros definidos por ABI o alguna combinación). de los dos) de tal manera que se puede pasar un número variable de argumentos, y cuando el destinatario regresa, la persona que llama es responsable de la limpieza (sacando el espacio del argumento usado de la pila, o en el caso de los registros, no se necesita nada para la limpieza). Esta configuración se presta perfectamente al código de inicio pasando más parámetros de los que podrían ser necesarios, y la implementación main() del usuario es libre de usar o no usar ninguno de estos argumentos, como es el caso con el tratamiento de varias plataformas de varias formas de main() lista en tu pregunta.Sin embargo, esta no es la única forma en que un compilador + vinculador podría lograr este objetivo: en cambio, el vinculador podría elegir entre varias versiones del código de inicio según la definición de su main(). Hacerlo permitiría una gran variedad de listas de argumentos principales() que de otro modo serían imposibles con el modelo de limpieza de llamadas cdecl. Y dado que todo eso está definido por la implementación, es legal según las especificaciones de C++, siempre que el compilador + vinculador admita al menos las dos combinaciones que se muestran arriba (int main() y int main(int, char**)).

Cuestiones relacionadas