Algunas de las características del lenguaje C comenzaron como hacks que simplemente funcionaron.
Varias firmas para listas de argumentos principales y de longitud variable es una de esas características.
Los programadores notaron que pueden pasar argumentos adicionales a una función y no pasa nada malo con su compilador dado.
Este es el caso si las convenciones de llamada son tales que:
- La función de llamada limpia los argumentos.
- Los argumentos más a la izquierda están más cerca de la parte superior de la pila, o de la base del marco de la pila, de modo que los argumentos falsos no invalidan el direccionamiento.
Un conjunto de convenciones de llamadas que obedecen estas reglas es el paso de parámetros basados en la pila, mediante el cual el llamador muestra los argumentos y se empujan de derecha a izquierda:
;; pseudo-assembly-language
;; main(argc, argv, envp); call
push envp ;; rightmost argument
push argv ;;
push argc ;; leftmost argument ends up on top of stack
call main
pop ;; caller cleans up
pop
pop
En los compiladores en los que se da este tipo de convención de llamada, no es necesario hacer nada especial para admitir los dos tipos maino incluso los tipos adicionales. mainpuede ser una función sin argumentos, en cuyo caso es ajeno a los elementos que se insertaron en la pila. Si es una función de dos argumentos, entonces busca argcy argvcomo los dos elementos de la pila más altos. Si es una variante de tres argumentos específica de la plataforma con un puntero de entorno (una extensión común), eso también funcionará: encontrará ese tercer argumento como el tercer elemento de la parte superior de la pila.
Por tanto, una llamada fija funciona para todos los casos, lo que permite vincular al programa un único módulo fijo de puesta en marcha. Ese módulo podría escribirse en C, como una función parecida a esta:
/* I'm adding envp to show that even a popular platform-specific variant
can be handled. */
extern int main(int argc, char **argv, char **envp);
void __start(void)
{
/* This is the real startup function for the executable.
It performs a bunch of library initialization. */
/* ... */
/* And then: */
exit(main(argc_from_somewhere, argv_from_somewhere, envp_from_somewhere));
}
En otras palabras, este módulo de inicio solo llama a un main de tres argumentos, siempre. Si main no toma argumentos, o solo int, char **, resulta que funciona bien, así como si no toma argumentos, debido a las convenciones de llamada.
Si tuviera que hacer este tipo de cosas en su programa, ISO C no sería portátil y lo consideraría un comportamiento indefinido: declarar y llamar a una función de una manera y definirla de otra. Pero el truco de inicio de un compilador no tiene por qué ser portátil; no se rige por las reglas de los programas portátiles.
Pero suponga que las convenciones de llamada son tales que no puede funcionar de esta manera. En ese caso, el compilador debe tratar mainespecialmente. Cuando se da cuenta de que está compilando la mainfunción, puede generar código que sea compatible con, digamos, una llamada de tres argumentos.
Es decir, escribe esto:
int main(void)
{
/* ... */
}
Pero cuando el compilador lo ve, esencialmente realiza una transformación de código para que la función que compila se parezca más a esto:
int main(int __argc_ignore, char **__argv_ignore, char **__envp_ignore)
{
/* ... */
}
excepto que los nombres __argc_ignoreno existen literalmente. No se introducen tales nombres en su ámbito y no habrá ninguna advertencia sobre los argumentos no utilizados. La transformación de código hace que el compilador emita código con el enlace correcto que sabe que tiene que limpiar tres argumentos.
Otra estrategia de implementación es que el compilador o quizás el enlazador genere la __startfunción de manera personalizada (o como se llame), o al menos seleccione una de varias alternativas precompiladas. La información podría almacenarse en el archivo de objeto sobre cuál de las formas admitidas de mainse está utilizando. El vinculador puede ver esta información y seleccionar la versión correcta del módulo de inicio que contiene una llamada a la mainque es compatible con la definición del programa. Las implementaciones de C generalmente tienen solo una pequeña cantidad de formas compatibles, por mainlo que este enfoque es factible.
Los compiladores para el lenguaje C99 siempre tienen que tratar mainespecialmente, hasta cierto punto, para soportar el truco que si la función termina sin una returndeclaración, el comportamiento es como si return 0se hubiera ejecutado. Esto, nuevamente, puede tratarse mediante una transformación de código. El compilador nota que mainse está compilando una función llamada . Luego comprueba si el final del cuerpo es potencialmente alcanzable. Si es así, inserta unreturn 0;
mainmétodo en un solo programa enC(o, en realidad, en casi cualquier lenguaje con tal construcción).