
Karthik Balagurú
¿Es posible evitar el punto de entrada (principal) en un programa C? En el siguiente código, ¿es posible invocar el func()
llamar sin llamar vía main()
en el siguiente programa? En caso afirmativo, ¿cómo hacerlo y cuándo sería necesario y por qué se da tal disposición?
int func(void)
{
printf("This is func \n");
return 0;
}
int main(void)
{
printf("This is main \n");
return 0;
}
Si está usando gcc, encontré un hilo que decía que puede usar el -e
parámetro de línea de comandos para especificar un punto de entrada diferente; para que puedas usar func
como su punto de entrada, lo que dejaría main
no usado.
Tenga en cuenta que esto en realidad 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 de libc: hace algunas configuraciones y luego eso llamadas main
. Entonces, 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 cosas como analizar 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.

rberteig
Al crear firmware de sistemas integrados para que se ejecute directamente desde la ROM, a menudo evito nombrar el punto de entrada main()
para enfatizar a un revisor de código la naturaleza especial del código. En estos casos, estoy proporcionando una versión personalizada del módulo de inicio de tiempo de ejecución de C, 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 cantidad sorprendente 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 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 al momento del encendido, casi siempre hay una cantidad de hardware de selección de chip o una FPGA o dos que requieren inicialización antes de que sea seguro dejar 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 se encarga de hacer que el entorno en el que main()
se llama existir. 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 cierta cantidad de trabajo de inicialización que hacer antes main()
puede ser llamado. 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á usando implícitamente para su cadena de herramientas y leerlo puede ser instructivo, al igual que el manual de referencia para el enlazador mismo y el estándar ABI que deben seguir sus ejecutables para ejecutarse.
Editar: Para responder más directamente a la pregunta formulada en un contexto más común: “¿Puedes llamar a foo en lugar de main?” La respuesta es “Tal vez, pero solo siendo engañoso”.
En Windows, un ejecutable y una DLL tienen casi el mismo formato de archivo. Es posible escribir un programa que cargue una DLL arbitraria nombrada en tiempo de ejecución, localice una función arbitraria dentro de ella y la llame. Uno de esos programas en realidad se envía como parte de una distribución estándar de Windows: rundll32.exe
.
Dado que un archivo .EXE puede ser cargado e inspeccionado por las mismas API que manejan archivos .DLL, en principio si el .EXE tiene una sección EXPORTAR que nombra la función foo
, entonces se podría escribir una utilidad similar para cargarla e invocarla. No necesitas hacer nada especial con main
, por supuesto, 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. (Busque en Google “DLL Hell” para obtener una pista). En ese caso, es posible que su utilidad deba ser más inteligente. Por ejemplo, podría actuar como un depurador, cargar el EXE con un punto de interrupción en main
corra hasta ese punto de ruptura, luego cambie la PC para que apunte a 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.
Una regla general sería que el cargador suministrado por el sistema siempre ejecutar principal. Con suficiente autoridad y competencia, teóricamente podría escribir un cargador diferente que hiciera otra cosa.
Cambie el nombre de main para que sea func y func para que sea main y llame a func desde 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 destinado a sistemas integrados, puede modificar el inicio del tiempo de ejecución de C (CRT) para que comience en cualquier punto de entrada que necesite. En GCC este código está en crt0.s. Por lo general, este código está parcial o totalmente en ensamblador; para la mayoría de los compiladores de sistemas integrados, se proporcionará un ejemplo o un código de inicio predeterminado.
Sin embargo, un enfoque más simple es simplemente ‘ocultar’ main() en una biblioteca estática que vincula a su código. Si esa implementación de main() se ve así:
int main(void)
{
func() ;
}
Luego, a todos los efectos, se verá como si el punto de entrada del usuario fuera func(). Así es como funcionan muchos marcos de aplicación con puntos de entrada distintos de main(). Tenga en cuenta que debido a que se encuentra en una biblioteca estática, cualquier definición de usuario de main() anulará esa versión de biblioteca estática.

Oleg
La solución depende del compilador y el enlazador que utilice. siempre es eso no main
es el punto de entrada real de la aplicación. El punto de entrada real hace algunas inicializaciones y llamadas, por ejemplo main
. Si escribe programas para Windows usando Visual Studio, puede usar el modificador /ENTRY del enlazador para sobrescribir el punto de entrada predeterminado mainCRTStartup
y llama func()
en lugar de main()
:
#ifdef NDEBUG
void mainCRTStartup()
{
ExitProcess (func());
}
#endif
Si es una práctica estándar si escribe la aplicación más pequeña. En ese caso, recibirá restricciones en el uso de las funciones de C-Runtime. Debe 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")
Deberías usar OutputString(TEXT("This is func \n"))
donde OutputString
se aplican sólo 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));
}

Gian
Esto realmente depende de cómo esté invocando el binario, y va a ser razonablemente específico de la plataforma y el entorno. La respuesta más obvia es simplemente cambiar el nombre del símbolo “principal” a otra cosa y llamar a “función” “principal”, pero sospecho que eso no es lo que está tratando de hacer.
¿Por qué necesitarías hacer eso?
– Oded
31 de julio de 2010 a las 17:49
En C++, el ctor de un objeto estático global puede ejecutarse antes que main().
– arena
31 de julio de 2010 a las 17:57
Para reformular la pregunta de Oded: Díganos lo que quiere lograr y le diremos cómo lograrlo, probablemente sin eludir
main
. (Más específicamente: algunos SOer lo son. Mi falta de conocimiento de C me impide ayudarlo).– MvanGeest
31 de julio de 2010 a las 18:00
Es una pregunta que encontré mientras discutía varias preguntas complicadas de C 🙂 Yo también me pregunto la necesidad y el uso de la misma.
– Karthik Balagurú
31 de julio de 2010 a las 18:01
En C – No. Sin embargo, algunos compiladores/plataformas pueden proporcionar medios para lograrlo. ¿Alguna plataforma en particular que tengas en mente?
– nos
31 de julio de 2010 a las 22:24