26/04/2024
# Tags
#C++ #Programación

Funciones VI – main()

El principio y el fin

Todo programa en C++ tiene principio en una función denominada main, en la función irá el código principal del programa y normalmente llamará a funciones especializadas para realizar las actividades para las que ha sido diseñado; y normalmente es el punto de término del programa, sin embargo es posible que el programa termine en otros puntos o  funciones por diversas razones (excepciones, errores, uso de exit, etc.)

Hasta ahora se ha utilizado una versión simplificada de main,

int main()

es momento de revisar esta función en detalle.

Sintaxis de main.

main(int argc, char *argv[ ], char *envp[ ] )
{
     // Programa
     return 0;
}

en donde,

argcargument count: Valor no negativo que indica el número de argumentos enviados al programa desde el entorno donde se ejecuta. El primer argumento siempre será el nombre del programa, de modo que si llamamos al programa sin argumentos, argc, siempre valdrá 1

argvargument vector: Puntero al primer elemento de una matriz de punteros a cadenas de texto terminadas en nulo que representan los argumentos pasados al programa desde el entorno de ejecución (argv[0] hasta argv[argc-1]). Se garantiza que el valor argv[argc] es un puntero nulo.

envp – que apunta a una matriz de punteros a variables de entorno de ejecución.

Nota: argc, argv, envp, son nombres arbitrarios y pueden llevar cualquier otro nombre

//programa – Aquí van las sentencias del programa.
return – Valor de retorno, el cual debe ser entero, normalmente si se devuelve 0 se supone que el programa termina normalmente. Si se omite por default el compilador devuelve 0 como valor de retorno.

Programa mínimo en C++

De este modo el programa mínimo que podemos escribir en C++ pasa por declarar la función main sin sentencias, declaracón de tipos ni llamadoa otras funciones, incluso sin valor de retorno.

// main.cpp
// Declaración de la función main
// Además declara el programa mínimo que podemos hacer en C++
// 2019, Por http://about.me/carlosgbr
// Versión 1
// Compilado en https://www.onlinegdb.com/online_c++_compiler
int main( int argc, const char* argv[] )
{
}

El ejecutar este programa obtendrás una salida aparentemente nula, aunque por «atrás» ocurren cosas interesantes que por ahora pasaré por alto.

Argumentos de main

Si haz utilizado algún programa por línea de comandos tal vez haz utilizado instrucciones tal vez parecidas a esto:

dir /o
ls -l

Los argumentos /o en el caso de dir y -l en el caso de ls, los podemos utilizar como parámetros que se pasan a nuestro programa desde la línea de comandos. Revisa el siguiente ejemplo.

// main2.cpp
// Uso de parámetros en main
// 2019, Por http://about.me/carlosgbr
// Adaptado de Default int main arguments in C/C++ en
// https://stackoverflow.com/questions/17045493/default-int-main-arguments-in-c-c
// Versión 1
// Compilado en https://www.onlinegdb.com/online_c++_compiler
#include <iostream>
int main( int argc, const char* argv[] )
{
	// Imprime cada argumento de la línea de comandos.
	for( int i = 0; i < argc; i++ )
	{
		std::cout << "Argumento "<< i << " = " << argv[i] << std::endl;
	}
}

Si ejecutas este programa sin ningún parámetro (lo ejecutas y ya, tal como ha sido con todos los programas hasta ahora) obtendrás la siguiente salida.

Ejecución sin argumentos
Ejecución sin argumentos

Si deseas pasar parámetros al programa primero debes revisar el entorno de programación que utilizas, si estás trabajando con Dev-C o con CodeBlocks, por ejemplo, debes generar un ejecutable y ejectuarlo desde la línea de comando para que puedas llamar al programa con parámetros.

Si tu compilador es en línea, normalmente el entorno te proporcionará un mecanismo para pasar parámetros a tu programa, en nuestro caso que utilizamos OnlineGDB antes de ejecutar el programa podemos escribir los parámetros en la ventana input, como puedes apreciar en la siguiente imagen.

Paso de parámetros a un programa en OnlineGDB
Paso de parámetros a un programa en OnlineGDB

Lo cual es equivalente a llamar a nuestro programa desde la línea de comando escribiendo:

nombreprograma Argumento1 Argumento2

y obtendremos la siguiente salida

Ejecución de programa con argumentos
Ejecución de programa con argumentos

Propiedades de la función main

La función main tiene varias propiedades especiales (1)(2) :

  1. No puede ser invocada explícitamente a lo largo del programa, es invocada de forma automática por el módulo de inicio
  2. No se puede utilizar en ningún lugar del programa
    1. No se puede llamar recursivamente
    2. No puede obtenerse su dirección, por lo tanto no pueden declararse punteros a ella
  3. No se puede predefinir ni sobrecargar
  4. No se puede definir como eliminada o declarada con enlazado de lenguaje C (desde C++17), inline, static, o constexpr.
  5. El cuerpo de la función main no necesita contener la sentencia return: si el control llega al final de main sin encontrar la sentencia return, es como ejecutar return 0;.
  6. 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.
  7. (desde C++14) No se puede deducir el tipo de retorno de la función main (no se permite auto main() {…}).

Hay más propiedades que he pasado por alto, en etapa no representa ningún problema no considerarlas.

Un poco más de main

Antes de que se ejecuten las sentencias que programamos, ya han ocurrido muchas cosas que preparan el entorno de ejecución de nuestros programas, como pueden ser la carga del programa, la inicialización de variables estáticas y algunas verificaciones de hardware, tras este proceso se pasa el control del programa a una función que debe llamarse main, y aquí es donde como programadores tomamos el control.

Si la función main llama a una función, main pasa el control a dicha función, cuando termina la ejecución de la función ésta devuelve el control a main cuando encuentra la sentencia return o cuando el código de la función termina. También recuerda que puedes terminar el programa en cualquier punto utilizando la función exit de la librería estándar.

return y main

Si el compilador encuentra la sentencia return, la utiliza como última instrucción. Como ya se ha mencionado, normalmente se devuelve un 0, este valor se entrega a quien llamó al programa, normalmente el sistema operativo. Nosotros podemos en caso de ser necesario definir un valor o conjunto de valores de salida para return.

Si no se declara una sentencia return, el compilador devolverá 0, indicando que todo salió bien.

Aunque normalmente un código de salida 0 significa que todo salió bien, puede darse la situación que una determinada plataforma o compilador realicen una interpretación del 0 y para algún caso particular 0 no signifique necesariamente «sin novedad». Para evitar este tipo de situaciones C++ recomienda que utilices las macros EXIT_FAILURE y EXIT_SUCCESS

Valor de returnComentarios
EXIT_FAILUREDevuelve una ejecución errónea
EXIT_SUCCESSindica al proceso padre que todo ha salido bien
EnteroDefinido por el programador para determinar una condición específica

Termino con un sencillo programa que utiliza la tabla anterior para indicar algunas condiciones específicas de salida y no la predeterminada (return 0)

// cpp_68_main_return.cpp
// Uso de parámetros en main como forma de recepción de datos
// para ilustrar diversas condiciones de salida de return
// El programa calcula el índice de masa corporal
// 2019, Por http://about.me/carlosgbr
// Versión 1
// Compilado en https://www.onlinegdb.com/online_c++_compiler
#include <iostream>
#include <sstream>
using namespace std;
// Este programa requiere obligatoriamente 3 argumentos de entrada
// modo de llamada: nombreprograma nombre estatura peso
// nombreprograma - el nombre del programa sin espacios varía como lo llames, dejar espacio
// nombre - tu nombre, dejar espacio
// peso - en kilogramos, dejar espacio
// estatura - en metros
// ejemplo: imc paulina 51 164
int main(int argc, const char* argv[])
{
// Verificamos que tenga los 3 parámetros obligatorios
if (argc != 4)
{
std::cout << "Número de parámetros incorrecto.";
return EXIT_FAILURE; // Terminamos el programa
}
else {
// Recuperamos los parámetros de la línea de comandos
string nombre = argv[1]; // El primero es el nombre del usuario
// Convertimos el peso en número
string speso = argv[2]; // Toma el segundo argumento y lo almacena
istringstream stream1(speso);
float peso;
stream1 >> peso; // hacemos la conversión a float
// convertimos estatura a float
string sestatura = argv[3];
istringstream stream2(sestatura);
float estatura;
stream2 >> estatura;
// Calculamos nuestro índice de masa corporal, estatura en metros
// peso en kilogramos.
float imc = peso / (estatura*estatura);
// Definimos nuestroc xódigos de retorno de a cuerdo a nuestra
// conveniencia, recuerda nosotros somos los responsables
// Una vez depurado puedes comentar las salidas cout
if (imc > 16) {
cout << nombre << " tu imc es: " << imc;
return EXIT_SUCCESS; // Fin de programa, correcto
} else if ((imc <= 16) and (imc > 10)) {
cout << nombre << " tu imc es: " << imc;
return 10; // nosotros definimos que significa 1, por
// ejemplo "revisar valores"
} else if ((imc <= 10) and (imc > 0)) {
cout << nombre << " tu imc es: " << imc;
return 20; // Revisar sino están invertidos los argumentos
} else {
cout << nombre << " tu imc es: " << imc;
return 30; // Error desconocido
}
}
cout << "Hola Mundo"; //Esta línea no debería ejecutarse dado que el
// programa termina en return.
}

Códigos de retorno personalizados en un programa

En el programa anterior (cpp_68_main_return.cpp) hacemos una variante del programa de cálculo del IMC (Índice de masa corporal) en este caso solo nos interesa el valor del IMC no su interpretación como fue en el programa cpp_20_imc2if.cpp, además por las características del diseño, únicamente nos interesan (realmente) los códigos de retorno que nosotros definimos en función del resultado obtenido. Naturalmente, es un programa simplificado cuyo objetivo es mostrar como puedes implementar y utilizar return, el resto del programa es solo un modo de obtener códigos de salida útiles.

Para que nuestro programa funcione de forma correcta, requiere que le pases 3 argumentos, nombre, peso, estatura, si alguno falta el programa se detiene y termina utilizando la macro EXIT_FAILURE y envía un mensaje. Nota que validamos el número de valores introducidos con if. (Ahora responde, ¿Porqué validamos 4 argumentos y no 3? si no encuentras la respuesta, revisa nuevamente este documento más arriba).

En esta imagen vemos una salida si alimentamos la entrada con el número incorrecto de argumentos:

Condición de error en código de salida
Condición de error en código de salida

Por otra parte nuestro programa espera los datos de una forma muy específica, peso en kilogramos (el usuario los podría poner en gramos), la estatura en metros (podrían ponerlo en centímetros dado que muchas calculadoras así lo usan). Como el programa de cualquier forma realizará un cálculo, es nuestra responsabilidad controlar el resultado e informar en medida de lo posible de potenciales errores, para esto utilizamos los códigos de salida con return. Nosotros en la documentación podemos indicar al usuario que al obtener un código de salida 30, por ejemplo, que revisen que no haya puesto a 0 algún valor.

A continuación te muestro varias corridas del programa con diferentes argumentos y su respectiva salida.

Parámetros de entrada

calculaimc Carlos 75 1.68

Salida

Código de Salida 0 (Correcto) recibe todos los argumentos de forma correcta
Código de Salida 0 (Correcto) recibe todos los argumentos de forma correcta


Parámetros de entrada

calculaimc Carlos 1.68

Salida

Condición de error en código de salida
Condición de error en código de salida


Parámetros de entrada

calculaimc Carlos 0 1.68

Salida

Código de salida 30, de acuerdo a nuestra programación, no tenemos forma de determinar el error.
Código de salida 30, de acuerdo a nuestra programación, no tenemos forma de determinar el error.

Concluyo esta sección mencionando que entre más condiciones de error quieras manejar y resolver de forma apropiada (y no simplemente decir «exit code 30 – Error desconocido) requieres codificar más variantes, de hecho todas las que quieras manejar: Evitar texto en los campos numéricos, tratamiento de cifras negativas, cifras que tengan caracteres y números I en lugar de 1, O en lugar de cero, por ejemplo.

main(), void main(), int main()

Termino este capítulo abordando una pregunta que suele ser recurrente de tiempo en tiempo. El tipo utilizado con mai().

void main()

Seamos concretos y tajantes, utilizar void main() es incorrecto, no lo utilices aunque el compilador que utilices te lo permita, el estándar actual y los compiladores modernos rechazarán el uso de

 void con main()  // Erróneo

int main()

El estándar actual requiere que main devuelva un entero, por tanto esta es la forma correcta de tipar a main(). El int devuelto por main() se devuelve al sistema que lo invoca, ya sea el sistema operativo u otro programa. Sino se invoca un valor de retorno, el valor de retorno se ignora. Por esta razón algunos programadores creen que el uso de void es adecuado.

Para zanjar esta posible controversia por parte de algunos usuarios que puedan utilizar void, tomo las palabras de Bjarne Stroustrup en su blog en la sección de preguntas y respuestas:

«Incluso si su compilador acepta «void main()» evítelo, o arriesgue siendo considerado ignorante por los programadores C y C++.»

«Even if your compiler accepts «void main()» avoid it, or risk being considered ignorant by C and C++ programmers. » Bjarne Stroustup.

int main()  // Correcto

main()

La mayoría de los compiladores aceptará esta forma, de forma automática se asigna un valor de retorno int a main(), sin embargo es preferible que utilices la forma formal int main()

int main()  // Correcto, preferible int main()

int main() o int main (int argc, const char* argv[])

En este caso no hay una regla definida, muchos entornos de desarrollo al iniciar la plantilla de un programa nuevo ya los incluyen, por lo que normalmente no nos ocupamos de este tema, sin embargo si los debes escribir manualmete, puedes omitirlos sino utilizas los argumentos en tu programa. Naturalmente siempre será mejor que te adhieras a los estándares, así que si insistieras en un respuesta, la sugerencia es que utilices la forma completa.

int main (int argc, const char* argv[])

Conclusión

Hay mucho más que se puede explorar de la fución main, sin embargo ya tienes un bosquejo inicial que te ilustra que main es más que un simple contenedor de código, tiene muchas características que podemos explotar aún en programas incluso básicos, si vas a programar utilerías, algunos exploits o cosas… «interesantes» te será de utilidad conocer y practicar el paso y uso de argumentos con main.

Así cierro el bloque dedicado a funciones. He intentado ofrecerte la estructura que te servirá para que con paso seguro puedas explorar y ampliar el universo que implican las funciones en C++. Recuerda entre más aprendemos, menos sabemos… pero podemos hacer más 😉


Ethical Hack

Referencias

Referencias complementarias para revisar el objeto string, isstring

Fuente Imágenes

  • «Todas las demás imágenes de esta sección»: by Nala Systems

Código Fuente

Licencia de Creative Commons
Funciones VI en C++ by Roberto C. González is licensed under a Creative Commons Reconocimiento-NoComercial-CompartirIgual 4.0 Internacional License.
Funciones VI – main()

Funciones V – Librerías

Leave a comment

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *