4.4.4 La función main
§1 Sinopsis
Durante la fase de enlazado de la compilación (
1.4.4 ), el "linker" añade a cualquier programa C++ un módulo especial,
de inicio,
que es realmente el punto de entrada a la ejecución del programa. Este módulo realiza diversas
tareas preparatorias a la ejecución propiamente dicha. Por ejemplo, iniciar
todas las variables estáticas o globales y realizar determinadas verificaciones del
hardware. Finalmente pasa el control a una
función que debe responder al nombre de main y le pasa algunos argumentos en base a datos que ha recibido a su vez del
Sistema Operativo; esta es la razón por la que todos los programas C++ deben contener una función con este nombre
[4]. Así pues, main representa el punto de la ejecución a partir del cual el programador toma el
control de la ejecución, antes de esto ya han sucedido muchas cosas en nuestro programa. A su vez el punto de finalización de
esta función, su punto de retorno (return) significa el fin del programa [3],
pero recuerde que existe otra forma de terminar el programa, que puede ser
utilizada desde cualquier punto (sin necesidad de volver a la función main),
consiste en utilizar la función exit (
1.5.1) de la Librería Estándar.
Nota: algunos compiladores permiten al programador la opción de que se llamen determinadas funciones
antes que se realice la llamada a main. Estas funciones representan
tareas preparatorias adicionales que queremos realizar antes de que se inicie la ejecución. Esto se consigue con la directiva
de inicio #pragma startup. En cualquier caso, las variables estáticas son inicializadas antes que estas funciones
iniciales (1.5).
§2 Argumentos
La función main es
imprescindible en cualquier programa C/C++ representa el punto de inicio de su
ejecución. Por lo general, su declaración adopta la forma:
int main();
aunque en realidad, el módulo de inicio la invoca con dos parámetros (recibidos a su vez del SO), denominados tradicionalmente argc y argv, contracciones de "argument count" y "argument vector" respectivamente. El primero es un entero que representa el número de comandos que se pasan; el segundo es un puntero a una matriz de cadenas literales de distintas longitudes (es decir: puntero a matriz de punteros); cada una de estas cadenas representa en último extremo los comandos iniciales que se quieren pasar al programa, generalmente para controlar aspectos de su comportamiento. Así pues, la declaración más genérica de main es del tipo:
int main(int argc, char* argv[]);
Nota: el Estándar establece que el compilador debe aceptar para main cualquiera de las dos formas anteriores.
Por convención, argv[0] es el nombre con que se ha llamado al programa (normalmente será el nombre del fichero
ejecutable incluyendo su dirección completa -path-). Este dato es proporcionado automáticamente por el SO; así pues, el valor
mínimo para argc es 1. Después seguirán los que introduzcamos en la línea de comandos, separados por espacios.
§2.1 Como ejemplo, puede usarse el siguiente programa, al que llamaremos prueba.exe:
#include <stdio.h> // Prueba de parámetros para función main
int main(int argc, char* argv[]) {
int i;
printf("Se han pasado %3d argumentos:\n", argc);
for(i=0; i<argc; i++) printf("%5d- %s\n", i, argv[i]);
return 0;
}
Si introducimos en la línea de comandos: prueba se pasan seis, parametros
, se obtiene la siguiente salida
[1]:
Se han pasado 6 argumentos:
1- D:\ZF\LEARNC\PRUEBA.EXE
2- prueba
3- se
4- pasan
5- seis,
6- parametros
Se ha dicho que argv es un puntero a matriz de punteros, y en el ejemplo hemos utilizado la expresión
argv[i], algo que a primera vista puede chocar, ya que argv no es una matriz. En realidad la declaración
completa: char* argv[] significa puntero-a-matriz-de-caracteres, para distinguirlo de char* argv que sería
simplemente puntero-a-carácter.
§2.2 Los argumentos anteriores son recogidos por el sistema en forma de sendas variables globales
_argc y _argv (
4.1.3a) definidas en la cabecera <dos.h>. A continuación se expone una variación
del programa anterior que utiliza estas variables:
#include <iostream>
using namespace std;
#include <dos.h> // necesario para _argc y argv
int main (int argc, char* argv[]) { // ==============
cout << "Se han pasado " << _argc << " argumentos:" << endl;
for (int i = 0; i < _argc; ++i)
cout << " " << i << "- " << _argv[i] << endl;
return 0;
}
La salida es la misma que en el caso anterior.
§3 Restricciones
La función main adolece de ciertas limitaciones que la diferencian del resto de funciones C++:
- No puede ser invocada explícitamente a lo largo del programa, es invocada de forma automática por el módulo
de inicio
.
No puede obtenerse su dirección, por lo tanto no pueden declararse punteros a ella:
int (* pmain)() = &main; // Error!!
- No puede ser sobrecargada (
4.4.1a).
- No puede ser declarada como inline (
4.4.6b).
- main debe estar en el espacio global de una de las unidades de compilación del programa, lo que significa que no puede pertenecer a una clase.
§4 Valor devuelto por main
En muchos sistemas operativos el valor devuelto por la función main es utilizado como control de estado para el entorno desde el que se ha ejecutado el programa (este valor es considerado el estado de retorno del programa). En UNIX, MS-DOS y MS Windows, los 8 bits más bajos del valor devuelto son pasados al programa invocante o al intérprete de comandos. Este "estado de retorno" se utiliza en ocasiones para cambiar el curso de un programa, un proceso por lotes o un guión para el ejecutor de comandos.
Algunos compiladores pueden generar un aviso de error si se intenta compilar un programa cuya función main no devuelva un int. Por contra, algunas plataformas pueden provocar fallos cuando estos programas arrancan o a su terminación, ya que esperan un int de retorno.
En los programas C++ no es necesario devolver nada desde la función main, aunque en realidad la definición de esta sea int main(). La razón es que el Estándar garantiza que si main llega a su final alcanzando el corchete de cierre "}" sin haber encontrado una sentencia return, el compilador debe devolver automáticamente un 0 indicando un final correcto, de forma que en ausencia de ningún retorno el compilador proporciona automáticamente un return 0; [2].
Nota: no obstante lo anterior, el comportamiento concreto varía de un compilador a otro. Por
ejemplo, un programa en el que se alcanza el corchete de cierre de la función main sin ninguna sentencia de retorno
compila sin dificultad ni aviso de ningún tipo en Borland C++ 5.5, mientras que MS Visual C++ 6.0 avisa: warning C4508:
'main' : function should return a value; 'void' return type assumed
.
En cualquier caso, es práctica de buena programación incluir un valor de retorno para la función main. Tradicionalmente 0 significa una terminación correcta del programa, cualquier otro valor es señal de terminación anormal (error o excepción).
Si el programa termina por una invocación a la función exit, el valor devuelto por main es el argumento
pasado a exit (
1.5.1). Por ejemplo, si el programa contiene la llamada exit(1), el valor devuelto es 1.
§4.1 Existen solamente tres valores completamente estándar y portables que puedan utilizarse como retorno
para la función main, o como argumento para la función exit:
- El clásico entero de valor 0.
- La constante simbólica (
1.4.1a) EXIT_SUCCESS definida en <stdlib.h>
- La constante EXIT_FAILURE definida en <stdlib.h>
Si se utiliza cualquiera de los dos valores 0 o EXIT_SUCCESS se garantiza que el compilador los trasladará a un código que sea considerado terminación correcta por el Sistema Operativo. Si se utiliza EXIT_FAILURE el compilador lo trasladará a un código que será considerado como terminación errónea por el Sistema Operativo.
§4.2 Algunos sistemas, como Unix, MS-DOS y Windows, truncan el entero pasado a exit o devuelto por
main, a un unsigned char, con objeto de utilizarlos en el fichero de órdenes del intérprete de comandos, en un
fichero de ejecución por lotes, o en el proceso que invocó el programa. Algunos programadores utilizan valores positivos para
indicar diferentes razones de fallo, excepción o finalización del programa, pero estos usos no son necesariamente portables ni
funcionan en todas las implementaciones. Sin embargo C++ proporciona la posibilidad de devolver valores que son totalmente
portables mediante la función atexit (
1.5.1).
[1] Naturalmente, el "path" de la segunda línea: "D:\ZF\LEARNC\" depende del entorno de trabajo.
[2] Con excepción de main, C++ no proporciona un valor de retorno automático para ninguna otra función. Si la función invocante espera un valor y la función llamada llega a su fin sin haber encontrado un return adecuado, se devuelve un valor que es basura.
[3] En los programas C++ destinados a correr en una plataforma convencional, el módulo de inicio
invoca a una función que debe responder al nombre main. Es por ejemplo, el caso de los programas C++ compilados para
correr en modo texto en plataformas Windows (utilizando una ventana DOS). Responden al estándar, y el módulo de inicio invoca
a la función main. En los días en que se desarrolló MS-DOS, Microsoft Corporation no era una empresa tan importante y
debía seguir el estándar. Sin embargo, en los que deben correr bajo sistemas Windows utilizando su interfaz gráfica (GUI), el
compilador establece que el módulo de inicio invoque una función de nombre WinMain. Esta función representa el punto de
entrada a este tipo de aplicaciones, y acepta un cierto número de parámetros (que son los mismos para aplicaciones Windows 3.x
y Win-32) y que no se corresponden con los usuales. Por ejemplo, el primer argumento no es "argument count", ni el
segundo una matriz de punteros a carácter
. Naturalmente esto se aparta del Estándar C++, y
representa otra de las peculiaridades establecidas por el gigante de Redmond para su Sistema
Operativo (
1.7.5.1).
[4] Esto es lo usual en programas que corren bajo control de un Sistema Operativo. Sin embargo, existen sistemas en los que el programa no corre bajo un SO; son sistemas especiales "Freestanding environments", en los que el programa controla todo el entorno de la máquina que los alberga. En estos casos es potestad de la aplicación la existencia de una función main.