tutorial - suma de n numeros en c++ con while
Evitar el principal(punto de entrada) en un programa en C (7)
Al crear firmware de sistemas integrados para ejecutarse directamente desde la ROM, a menudo evitaré nombrar el punto de entrada main()
para enfatizar ante un revisor de códigos la naturaleza especial del código. En estos casos, estoy suministrando una versión personalizada del módulo de inicio de C runtime, por lo que es fácil reemplazar su llamada a main()
con otro nombre como BootLoader()
.
Yo (o mi proveedor) casi siempre tengo que personalizar el inicio del tiempo de ejecución de C en estos sistemas porque no es inusual que la RAM requiera un código de inicialización para que comience a funcionar correctamente. Por ejemplo, los chips DRAM típicos requieren una sorprendente cantidad de configuración de su hardware de control y, a menudo, requieren un retraso sustancial (miles de ciclos de reloj de bus) antes de que sean útiles. Hasta que esto se complete, es posible que ni siquiera haya un lugar para colocar la pila de llamadas, por lo que es posible que el código de inicio no pueda llamar a ninguna función. Incluso si los dispositivos RAM están operativos en el encendido, casi siempre hay una cierta cantidad de hardware de selección de chip o un FPGA o dos que requieren una inicialización antes de que sea seguro permitir que el tiempo de ejecución de C comience su inicialización.
Cuando un programa escrito en C se carga y se inicia, algún componente es responsable de hacer que exista el entorno en el que se llama main()
. En Unix, Linux, Windows y otros entornos interactivos, gran parte de ese esfuerzo es una consecuencia natural del componente del sistema operativo que carga el programa. Sin embargo, incluso en estos entornos hay una cantidad de trabajo de inicialización que debe realizarse antes de que se pueda llamar a main()
. Si el código es realmente C ++, entonces puede haber una cantidad sustancial de trabajo que incluye llamar a los constructores para todas las instancias de objetos globales.
Los detalles de todo esto son manejados por el enlazador y sus archivos de configuración y control. El enlazador ld (1) tiene un archivo de control muy elaborado que le dice exactamente qué segmentos incluir en la salida, en qué direcciones y en qué orden. Encontrar el archivo de control del enlazador que está utilizando implícitamente para su cadena de herramientas y leerlo puede ser instructivo, al igual que el manual de referencia del propio enlazador y el estándar ABI que deben seguir sus ejecutables para poder ejecutarse.
Edición: para responder de manera más directa a la pregunta como se hace en un contexto más común: "¿Se puede llamar a foo en lugar de main?" La respuesta es "Tal vez, pero solo por ser complicado".
En Windows, un ejecutable y una DLL son casi el mismo formato de archivo. Es posible escribir un programa que carga una DLL arbitraria llamada en tiempo de ejecución, localiza una función arbitraria dentro de ella y la llama. Uno de estos programas se distribuye como parte de una distribución estándar de Windows: rundll32.exe
.
Dado que un mismo archivo .EXE puede cargarse e inspeccionarse con las mismas API que manejan los archivos .DLL, en principio, si el archivo .EXE tiene una sección de EXPORTACIONES que nombra la función foo
, se podría escribir una utilidad similar para cargarla e invocarla. Por supuesto, no es necesario que haga nada especial con main
, ya que ese será el punto de entrada natural. Por supuesto, el tiempo de ejecución de C que se inicializó en su utilidad podría no ser el mismo tiempo de ejecución de C que se vinculó con su ejecutable. (Google para "DLL Hell" como sugerencia). En ese caso, su utilidad podría necesitar ser más inteligente. Por ejemplo, podría actuar como un depurador, cargar el EXE con un punto de interrupción en el sistema main
, ejecutarlo hasta ese punto de ruptura, luego cambiar la PC para que apunte hacia o hacia foo
y continuar desde allí.
Algún tipo de truco similar podría ser posible en Linux, ya que los archivos .so también son similares en algunos aspectos a los verdaderos ejecutables. Ciertamente, el enfoque de actuar como un depurador podría funcionar.
¿Es posible evitar el punto de entrada (principal) en un programa en C? En el siguiente código, ¿es posible invocar la llamada func()
sin llamar a través de main()
en el programa siguiente? Si la respuesta es Sí, ¿cómo hacerlo y cuándo sería necesario y por qué se otorga tal disposición?
int func(void)
{
printf("This is func /n");
return 0;
}
int main(void)
{
printf("This is main /n");
return 0;
}
Esto realmente depende de cómo invoque el binario y va a ser razonablemente específico para la plataforma y el entorno. La respuesta más obvia es simplemente cambiar el nombre del símbolo "principal" a otra cosa y llamar "func" "principal", pero sospecho que eso no es lo que estás tratando de hacer.
La solución depende del compilador y el enlazador que utilice. Siempre es que no main
es el punto de entrada real de la aplicación. El punto de entrada real hace algunas inicializaciones y convoca por ejemplo main
. Si escribe programas para Windows con Visual Studio, puede usar el interruptor / ENTRY del vinculador para sobrescribir el punto de entrada predeterminado mainCRTStartup
y call func()
lugar de main()
:
#ifdef NDEBUG
void mainCRTStartup()
{
ExitProcess (func());
}
#endif
Si es una práctica estándar si escribes la aplicación más pequeña. En el caso, recibirá restricciones en el uso de las funciones de C-Runtime. Debería usar la función API de Windows en lugar de la función C-Runtime. Por ejemplo, en lugar de printf("This is func /n")
, debe usar OutputString(TEXT("This is func /n"))
donde OutputString
se implementa solo con respecto a WriteFile
o WriteConsole
:
static HANDLE g_hStdOutput = INVALID_HANDLE_VALUE;
static BOOL g_bConsoleOutput = TRUE;
BOOL InitializeStdOutput()
{
g_hStdOutput = GetStdHandle (STD_OUTPUT_HANDLE);
if (g_hStdOutput == INVALID_HANDLE_VALUE)
return FALSE;
g_bConsoleOutput = (GetFileType (g_hStdOutput) & ~FILE_TYPE_REMOTE) != FILE_TYPE_DISK;
#ifdef UNICODE
if (!g_bConsoleOutput && GetFileSize (g_hStdOutput, NULL) == 0) {
DWORD n;
WriteFile (g_hStdOutput, "/xFF/xFE", 2, &n, NULL);
}
#endif
return TRUE;
}
void Output (LPCTSTR pszString, UINT uStringLength)
{
DWORD n;
if (g_bConsoleOutput) {
#ifdef UNICODE
WriteConsole (g_hStdOutput, pszString, uStringLength, &n, NULL);
#else
CHAR szOemString[MAX_PATH];
CharToOem (pszString, szOemString);
WriteConsole (g_hStdOutput, szOemString, uStringLength, &n, NULL);
#endif
}
else
#ifdef UNICODE
WriteFile (g_hStdOutput, pszString, uStringLength * sizeof (TCHAR), &n, NULL);
#else
{
//PSTR pszOemString = _alloca ((uStringLength + sizeof(DWORD)));
CHAR szOemString[MAX_PATH];
CharToOem (pszString, szOemString);
WriteFile (g_hStdOutput, szOemString, uStringLength, &n, NULL);
}
#endif
}
void OutputString (LPCTSTR pszString)
{
Output (pszString, lstrlen (pszString));
}
Renombra main a ser func y func para ser main y llama a func del nombre.
Si tiene acceso a la fuente, puede hacerlo y es fácil.
Si está utilizando un compilador de código abierto como GCC o un compilador dirigido a sistemas integrados, puede modificar el inicio de tiempo de ejecución de C (CRT) para comenzar en cualquier punto de entrada que necesite. En GCC este código está en crt0.s. En general, este código se encuentra parcial o totalmente en ensamblador, para la mayoría de los compiladores de sistemas integrados, se proporcionará un código de inicio predeterminado.
Sin embargo, un enfoque más simple es simplemente "ocultar" main () en una biblioteca estática que vinculas con tu código. Si esa implementación de main () se ve así:
int main(void)
{
func() ;
}
Luego verá todos los intentos y propósitos como si el punto de entrada del usuario fuera func (). Esta es la cantidad de marcos de aplicaciones con puntos de entrada que no sean main (). Tenga en cuenta que debido a que está en una biblioteca estática, cualquier definición de usuario de main () anulará esa versión de la biblioteca estática.
Si estás usando gcc, encontré un hilo que decía que puedes usar el parámetro de línea de comando -e
para especificar un punto de entrada diferente; por lo que podría utilizar func
como su punto de entrada, lo que dejaría el main
sin usar.
Tenga en cuenta que esto no le permite llamar a otra rutina en lugar de main
. En su lugar, le permite llamar a otra rutina en lugar de _start
, que es la rutina de inicio libc: realiza una configuración y luego llama main
. Por lo tanto, si hace esto, perderá parte del código de inicialización que está integrado en su biblioteca de tiempo de ejecución, que podría incluir elementos como el análisis de los argumentos de la línea de comandos. Lea sobre este parámetro antes de usarlo.
Si está utilizando otro compilador, puede haber o no un parámetro para esto.
Una regla de oro sería que el cargador suministrado por el sistema siempre se ejecutaría main. Con suficiente autoridad y competencia, teóricamente podrías escribir un cargador diferente que hiciera otra cosa.