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

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

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

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

person Karthik Balaguru    schedule 31.07.2010    source источник
comment
Зачем тебе это нужно?   -  person Oded    schedule 31.07.2010
comment
В C++ ctor глобального статического объекта может запускаться перед main().   -  person seand    schedule 31.07.2010
comment
Перефразируя вопрос Одеда: скажите нам, чего вы хотите достичь, и мы расскажем вам, как этого достичь, возможно, не обходя main. (Более конкретно: некоторые SO-специалисты. Недостаток знаний C мешает мне помочь вам.)   -  person MvanGeest    schedule 31.07.2010
comment
Это вопрос, с которым я столкнулся при обсуждении различных сложных вопросов C :-) Я тоже задаюсь вопросом о необходимости и использовании этого.   -  person Karthik Balaguru    schedule 31.07.2010
comment
В C - нет. Некоторые компиляторы/платформы могут предоставить средства для этого. Вы имеете в виду какую-то конкретную платформу?   -  person nos    schedule 01.08.2010
comment
Кто должен вызывать main или func? Стандартная операционная система? Пользовательский exe-загрузчик, возможно, встроенный в уже запущенную программу?   -  person zvrba    schedule 01.08.2010


Ответы (7)


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

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

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

person Joe White    schedule 31.07.2010
comment
Интересная информация. +1 за это. Предусматривает ли он также возможность определения наличия другой точки входа? - person Karthik Balaguru; 01.08.2010
comment
Вероятно, нет, поскольку этот параметр влияет на компоновщик, а не на компилятор. Но зачем вам нужно его обнаруживать? Вы тот, кто компилирует ваше приложение, поэтому вы будете знать, строите ли вы его таким образом или нет. - person Joe White; 02.08.2010
comment
Вы также можете использовать команду ENTRY в компоновщике. См. ftp.gnu.org/old-gnu /Руководства/ld-2.9.1/html_node/ld_24.html - person mashrur; 06.06.2019

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

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

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

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

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

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

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

Некоторые подобные трюки могут быть возможны в Linux, поскольку файлы .so также в некоторых отношениях похожи на настоящие исполняемые файлы. Конечно, можно заставить работать отладчик.

person RBerteig    schedule 31.07.2010
comment
Возможно, больше, чем когда-либо хотел знать ОП, но в любом случае увлекательное чтение! - person Carl Smotricz; 01.08.2010
comment
@Carl, даже если вам никогда не понадобится использовать эти знания, может быть полезно время от времени заглядывать под капот .... и попытка реализовать компоновщик - чрезвычайно поучительное упражнение ;-) - person RBerteig; 01.08.2010
comment
О, абсолютно! На самом деле я сделал что-то подобное для встраиваемой системы около 20 лет назад, переместив скомпилированный код Turbo C для встраиваемой системы. Тем не менее, я рад, что мне больше не придется этого делать :) - person Carl Smotricz; 01.08.2010
comment
Создание загрузчика для размещения EXE в ПЗУ также является занимательным занятием. Я ненавижу вести себя как старый чудак в этот момент, но есть искушение поныть о том, что этим молодым людям никогда не приходится на самом деле видеть биты, которые они используют. Затем перейдем к рассказам о ручной загрузке загрузчика с помощью переключателей на передней панели, чтобы считыватель с бумажной ленты мог загрузить настоящую программу .... ;-) - person RBerteig; 01.08.2010

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

person dmckee --- ex-moderator kitten    schedule 31.07.2010

Переименуйте main в func и func в main и вызовите func по имени.

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

person Community    schedule 24.01.2011

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

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

int main(void)
{
    func() ;
}

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

person Clifford    schedule 31.07.2010

Решение зависит от компилятора и компоновщика, которые вы используете. Всегда бывает так, что не 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));
}
person Oleg    schedule 01.08.2010

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

person Gian    schedule 31.07.2010