Избегание основной (точки входа) в программе C

Можно ли избежать точки входа (основной) в программе на C. В приведенном ниже коде можно ли вызывать вызов func() без вызова через main() в следующей программе? Если да, как это сделать и когда это потребуется, и почему такое положение дано?

int func(void)
{
     printf("This is func \n");
     return 0;
}

int main(void)
{
     printf("This is main \n");
     return 0;
}

Ответ 1

Если вы используете gcc, я нашел поток, который сказал, что вы можете использовать параметр командной строки -e, чтобы указать другое входная точка; поэтому вы можете использовать func как свою точку входа, которая оставила бы main неиспользованным.

Обратите внимание, что это фактически не позволяет вам вызывать другую процедуру вместо main. Вместо этого он позволяет вам вызывать другую процедуру вместо _start, которая является процедурой запуска libc - она ​​выполняет некоторую настройку, а затем вызывает main. Поэтому, если вы это сделаете, вы потеряете часть кода инициализации, встроенную в вашу библиотеку времени выполнения, которая может включать в себя такие вещи, как анализ аргументов командной строки. Перед использованием прочтите этот параметр.

Если вы используете другой компилятор, это может быть или не быть.

Ответ 2

При создании встроенного программного обеспечения встроенных систем для запуска непосредственно из ПЗУ, я часто избегаю названия точки входа main(), чтобы подчеркнуть для обозревателя кода особый характер кода. В этих случаях я предоставляю настраиваемую версию модуля запуска C, поэтому легко заменить его вызов на main() другим именем, например BootLoader().

Мне (или моему поставщику) почти всегда приходится настраивать запуск C в этих системах, поскольку для ОЗУ не является необычным требовать, чтобы код инициализации начал правильно работать. Например, типичные чипы DRAM требуют удивительной конфигурации своих аппаратных средств управления и часто требуют значительной задержки (тысячи циклов шины), прежде чем они будут полезны. Пока это не будет завершено, может быть даже не место для размещения стека вызовов, чтобы код запуска не смог вызвать какие-либо функции. Даже если операторы RAM работают при включении питания, почти всегда есть некоторое количество оборудования для выбора микросхем или FPGA или два, которые требуют инициализации, прежде чем можно безопасно позволить запуску C запустить свою инициализацию.

Когда программа, написанная на C, загружается и запускается, некоторый компонент отвечает за создание среды, в которой вызывается main(). В Unix, Linux, Windows и других интерактивных средах большая часть этих усилий является естественным следствием компонента ОС, который загружает программу. Однако даже в этих средах существует некоторая работа по инициализации перед тем, как main() можно вызвать. Если код действительно С++, тогда может быть значительная работа, включающая вызов конструкторов для всех экземпляров глобальных объектов.

Детали всего этого обрабатываются компоновщиком и его файлами конфигурации и управления. У компоновщика ld (1) есть очень сложный файл управления, который сообщает ему, какие именно сегменты должны включать в вывод, по каким адресам и в каком порядке. Поиск файла управления компоновщиком, который вы неявно используете для вашей инструментальной цепочки, и его чтение, может быть поучительным, так же как и справочное руководство для самого компоновщика и стандарта ABI, для выполнения которого должны выполняться ваши исполняемые файлы.

Изменить: Чтобы более точно ответить на вопрос, заданный в более общем контексте: "Можете ли вы назвать foo вместо основного?" Ответ: "Может быть, но только из-за сложностей".

В Windows исполняемый файл и DLL - это почти тот же формат файла. Можно написать программу, которая загружает произвольную DLL, названную во время выполнения, и находит в ней произвольную функцию и вызывает ее. Одна такая программа фактически поставляется как часть стандартного дистрибутива Windows: rundll32.exe.

Так как файл .EXE может быть загружен и проверен теми же API-интерфейсами, которые обрабатывают файлы .DLL, в принципе, если .EXE имеет раздел EXPORTS, который называет функцию foo, тогда аналогичная утилита может быть записана для загрузки и вызвать его. Разумеется, вам не нужно ничего особенного делать с main, так как это будет естественная точка входа. Конечно, среда выполнения C, которая была инициализирована в вашей утилите, может быть не такой же C-средой выполнения, которая была связана с вашим исполняемым файлом. (Google для "DLL Hell" для подсказки.) В этом случае ваша утилита, возможно, должна быть более умной. Например, он может выступать в качестве отладчика, загружать EXE с точкой прерывания в main, запускаться до этой точки прерывания, затем менять ПК на точку или в foo и продолжать оттуда.

В Linux может возникнуть какая-то подобная обманка, поскольку файлы .so также похожи в некоторых отношениях на истинные исполняемые файлы. Разумеется, подход к работе, как отладчик, можно заставить работать.

Ответ 3

Правило большого пальца будет заключаться в том, что загрузчик, поставляемый системой, всегда будет работать главным образом. Имея достаточные полномочия и компетентность, теоретически можно написать другой загрузчик, который сделал что-то еще.

Ответ 4

Переименуйте main, чтобы func и func были основными и вызывали func от имени.

Если у вас есть доступ к источнику, вы можете сделать это и легко.

Ответ 5

Если вы используете компилятор с открытым исходным кодом, такой как GCC или компилятор, предназначенный для встроенных систем, вы можете изменить запуск C (CRT), чтобы начать с любой точки входа, в которой вы нуждаетесь. В GCC этот код находится в crt0.s. Обычно этот код частично или полностью используется в ассемблере, для большинства примеров компиляторов встроенных систем или кода запуска по умолчанию.

Однако более простой подход - просто "скрыть" main() в статической библиотеке, которую вы связываете с вашим кодом. Если эта реализация main() выглядит так:

int main(void)
{
    func() ;
}

Затем он будет выглядеть во всех смыслах и целях, как если бы точка входа пользователя была func(). Это то, как работают рамки приложения с точками входа, отличными от main(). Обратите внимание, что поскольку он находится в статической библиотеке, любое определение пользователя main() будет переопределять эту статическую библиотечную версию.

Ответ 6

Решение зависит от используемого вами компилятора и компоновщика. Всегда, что не main - это настоящая точка входа приложения. Реальная точка входа делает некоторые инициализации и вызывает, например, main. Если вы пишете программы для Windows с помощью Visual Studio, вы можете использовать/ENTRY-переключатель компоновщика, чтобы перезаписать точку входа по умолчанию mainCRTStartup и вызвать func() вместо main():

#ifdef NDEBUG
void mainCRTStartup()
{
    ExitProcess (func());
}
#endif

Если это стандартная практика, если вы пишете самое маленькое приложение. В случае, если вы будете получать ограничения в использовании функций C-Runtime. Вы должны использовать функцию Windows API вместо функции C-Runtime. Например, вместо printf("This is func \n") вы должны использовать OutputString(TEXT("This is func \n")), где OutputString реализованы только в отношении WriteFile или 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));
}

Ответ 7

Это действительно зависит от того, как вы вызываете двоичный файл, и будет иметь определенную платформу и среду. Самый очевидный ответ - просто переименовать "главный" символ в нечто другое и вызвать "func" "main", но я подозреваю, что не то, что вы пытаетесь сделать.