#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
? как?
#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
? как?
Язык 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
Попробуйте использовать gcc -E source.c
, вывод заканчивается на:
int main()
{
printf("Ha HA see how it is?? ");
}
Таким образом, функция main()
фактически создается препроцессором.
Программа, о которой идет речь, вызывает 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, но их компиляция и их запуск, безусловно, очень возможны, даже если они нарушают спецификацию.
Кто-то пытается действовать как Маг.
Он думает, что может обмануть нас. Но мы все знаем, 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
decode(a,b,c,d,[...])
перетасовывает первые четыре аргумента и присоединяет их для получения нового идентификатора в порядке dacb
. (Остальные три аргумента игнорируются.) Например, decode(a,n,i,m,[...])
дает идентификатор main
. Обратите внимание, что это макрос begin
определяется как.
Следовательно, макрос begin
просто определяется как main
.
В вашем примере функция 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
.
Это до компоновщика в качестве инструмента операционной системы для установки точки входа, а не самого языка. Вы даже можете установить собственную точку входа, и вы можете создать также исполняемую библиотеку!