Этот запутанный C-код утверждает, что работает без main(), но что он на самом деле делает?

#include <stdio.h>
#define decode(s,t,u,m,p,e,d) m##s##u##t
#define begin decode(a,n,i,m,a,t,e)

int begin()
{
    printf("Ha HA see how it is?? ");
}

Это косвенно вызывает main? как?

Ответ 1

Язык C определяет среду выполнения в двух категориях: автономный и размещенный. В обоих средах исполнения функция вызывается средой для запуска программы.
В автономной среде функция запуска программы может быть реализована в хост-среде, она должна быть main. Никакая программа в C не может запускаться без функции запуска программы в определенных средах.

В вашем случае main скрывается определениями препроцессора. begin() будет расширяться до decode(a,n,i,m,a,t,e), который далее будет расширен до main.

int begin() -> int decode(a,n,i,m,a,t,e)() -> int m##a##i##n() -> int main() 

decode(s,t,u,m,p,e,d) представляет собой параметризованный макрос с 7 параметрами. Список замены для этого макроса m##s##u##t. m, s, u и t - это 4 th 1 st 3 rd и 2 nd параметр, используемый в список замены.

s, t, u, m, p, e, d
1  2  3  4  5  6  7

Отдых бесполезен (просто для обфускации). Аргумент, переданный в decode, имеет значение a, n, i, m, a, t, e " поэтому идентификаторы m, s, u и t заменяются аргументами m, a, i и n соответственно.

 m --> m  
 s --> a 
 u --> i 
 t --> n

Ответ 2

Попробуйте использовать gcc -E source.c, вывод заканчивается на:

int main()
{
    printf("Ha HA see how it is?? ");
}

Таким образом, функция main() фактически создается препроцессором.

Ответ 3

Программа, о которой идет речь, вызывает main() из-за расширения макросов, но ваше предположение является ошибочным - ему вообще не нужно называть main()!

Строго говоря, вы можете иметь программу на C и иметь возможность компилировать ее без символа main. main - это то, что ожидает c library, после того, как оно завершит свою собственную инициализацию. Обычно вы переходите в main из символа libc, известного как _start. Всегда возможно иметь очень действительную программу, которая просто выполняет сборку, не имея основной. Взгляните на это:

/* This must be compiled with the flag -nostdlib because otherwise the
 * linker will complain about multiple definitions of the symbol _start
 * (one here and one in glibc) and a missing reference to symbol main
 * (that the libc expects to be linked against).
 */

void
_start ()
{
    /* calling the write system call, with the arguments in this order:
     * 1. the stdout file descriptor
     * 2. the buffer we want to print (Here it just a string literal).
     * 3. the amount of bytes we want to write.
     */
    asm ("int $0x80"::"a"(4), "b"(1), "c"("Hello world!\n"), "d"(13));
    asm ("int $0x80"::"a"(1), "b"(0)); /* calling exit syscall, with the argument to be 0 */
}

Скомпилируйте приведенное выше с помощью gcc -nostdlib without_main.c и посмотрите, как он печатает Hello World! на экране, просто выдавая системные вызовы (прерывания) в встроенной сборке.

Для получения дополнительной информации об этой конкретной проблеме посетите блог ksplice

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

/* These values are extracted from the decimal representation of the instructions
 * of a hello world program written in asm, that gdb provides.
 */
const int main[] = {
    -443987883, 440, 113408, -1922629632,
    4149, 899584, 84869120, 15544,
    266023168, 1818576901, 1461743468, 1684828783,
    -1017312735
};

Значения в массиве - это байты, соответствующие инструкциям, необходимым для печати Hello World на экране. Более подробную информацию о том, как работает эта конкретная программа, можно найти в этом сообщении в блоге, в котором я также прочитал его в первую очередь.

Я хочу сделать последнее замечание об этих программах. Я не знаю, регистрируются ли они как действительные C-программы в соответствии со спецификацией языка C, но их компиляция и их запуск, безусловно, очень возможны, даже если они нарушают спецификацию.

Ответ 4

Кто-то пытается действовать как Маг. Он думает, что может обмануть нас. Но мы все знаем, c выполнение программы начинается с main().

int begin() будет заменен на decode(a,n,i,m,a,t,e) на один проход препроцессора. Опять же, decode(a,n,i,m,a,t,e) будет заменено на m ## a ## я ## n. Как и в случае позиционной ассоциации макровызов, s будет иметь значение символа a. Аналогично, u будет заменено на "i", а t будет заменено на "n". И, что, m##s##u##t станет main

Что касается символа ## в расширении макроса, он является оператором предварительной обработки и выполняет вставку маркера. Когда макрос расширяется, два токена с каждой стороны каждого оператора ## объединены в один токен, который затем заменяет "## и два оригинальных токена в расширении макроса".

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

gcc -E FILENAME.c

Ответ 5

decode(a,b,c,d,[...]) перетасовывает первые четыре аргумента и присоединяет их для получения нового идентификатора в порядке dacb. (Остальные три аргумента игнорируются.) Например, decode(a,n,i,m,[...]) дает идентификатор main. Обратите внимание, что это макрос begin определяется как.

Следовательно, макрос begin просто определяется как main.

Ответ 6

В вашем примере функция main() фактически присутствует, потому что begin - это макрос, который компилятор заменяет макросом decode, который, в свою очередь, заменяется выражением m ## s ## u ## t. Используя расширение макроса ##, вы достигнете слова main от decode. Это трассировка:

begin --> decode(a,n,i,m,a,t,e) --> m##parameter1##parameter3##parameter2 ---> main

Это просто трюк, чтобы иметь main(), но использование имени main() для функции ввода программы не требуется на языке программирования C. Это зависит от ваших операционных систем и компоновщика как одного из его инструментов.

В Windows вы не всегда используете main(), но скорее WinMain или wWinMain, хотя вы можете использовать main(), даже с инструментальной цепочкой Microsoft. В Linux можно использовать _start.

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