#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
c-preprocessor
obfuscation
Раджив Сингх
источник
источник
Ответы:
Язык 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- м , 1- м , 3- м и 2- м параметрами, используемыми в списке замены.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
, соответственно.источник
_start()
. Или даже на более низком уровне, я могу попытаться просто выровнять начало моей программы с адресом, на который устанавливается IP после загрузки.main()
является C Стандартной библиотеки . Сам C не накладывает на это ограничений.decode(a,n,i,m,a,t,e)
статьm##a##i##n
? Заменяет ли он персонажей? Можете дать ссылку на документацию поdecode
функции? Благодарю.begin
определяется для замены на то,decode(a,n,i,m,a,t,e)
что определено ранее. Эта функция принимает аргументыs,t,u,m,p,e,d
и объединяет их в этой формеm##s##u##t
(##
означает объединение). Т.е. игнорируются значения p, e и d. Когда вы «называете»decode
s = a, t = n, u = i, m = m, он эффективно заменяетсяbegin
наmain
.Попробуйте использовать
gcc -E source.c
, вывод заканчивается на:int main() { printf("Ha HA see how it is?? "); }
Таким образом,
main()
функция фактически создается препроцессором.источник
Рассматриваемая программа действительно вызывается
main()
из-за расширения макроса, но ваше предположение ошибочно - она вообще не должна вызыватьmain()
!Строго говоря, у вас может быть программа на C и у вас есть возможность компилировать ее без
main
символа.main
- это то, к чемуc library
объект ожидает перейти после завершения собственной инициализации. Обычно вы переходитеmain
с символа libc, известного как_start
. Всегда можно иметь очень правильную программу, которая просто выполняет сборку, не имея файла main. Взгляните на это:/* 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's 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, но их компиляция и запуск, безусловно, очень возможны, даже если они нарушают саму спецификацию.
источник
_start
частью определенного стандарта или это просто зависит от реализации? Конечно, ваш «основной массив как массив» зависит от архитектуры. Также важно, что ваш трюк с «main as an array» не будет лишним во время выполнения из-за ограничений безопасности (хотя это было бы более вероятно, если бы вы не использовалиconst
квалификатор, и все же многие системы разрешили бы это)._start
не входит в стандарт ELF, хотя AMD64 psABI содержит ссылку_start
на 3.4 Инициализация процесса . Официально ELF знает только адресe_entry
в заголовке ELF,_start
это просто имя, выбранное реализацией.const
не имеет значения ни один бит - имя символа в этом двоичном исполняемом файлеmain
. Ни больше ни меньше.const
это конструкция C, которая ничего не значит во время выполнения.Кто-то пытается вести себя как Волшебник. Он думает, что сможет нас обмануть. Но все мы знаем, что выполнение программы c начинается с
main()
.int begin()
Будут замененыdecode(a,n,i,m,a,t,e)
на один проход стадии препроцессора. Затем сноваdecode(a,n,i,m,a,t,e)
будет заменено на m ## a ## i ## n. Как и при позиционной ассоциации вызова макроса,s
воля будет иметь значение символаa
. Точно так жеu
будет заменено на «i» иt
будет заменено на «n». И вот какm##s##u##t
станетmain
Что касается
##
символа в раскрытии макроса, это оператор предварительной обработки, который выполняет вставку токена. Когда макрос раскрывается, два токена по обе стороны от каждого оператора '##' объединяются в один токен, который затем заменяет '##' и два исходных токена в раскрытии макроса.Если вы мне не верите, вы можете скомпилировать свой код с помощью
-E
flag. Он остановит процесс компиляции после предварительной обработки, и вы сможете увидеть результат вставки токена.источник
decode(a,b,c,d,[...])
перемешивает первые четыре аргумента и объединяет их, чтобы получить новый идентификатор в указанном порядкеdacb
. (Остальные три аргумента игнорируются.) Например,decode(a,n,i,m,[...])
дает идентификаторmain
. Обратите внимание, что это то, какbegin
определен макрос.Следовательно,
begin
макрос просто определяется какmain
.источник
В вашем примере
main()
функция фактически присутствует, потому чтоbegin
это макрос, который компилятор заменяетdecode
макросом, который, в свою очередь, заменяется выражением m ## s ## u ## t. Используя расширение макроса##
, вы дойдете до словаmain
изdecode
. Это след:Это просто уловка
main()
, но использование имениmain()
для функции входа в программу не обязательно в языке программирования C. Это зависит от ваших операционных систем и компоновщика как одного из его инструментов.В Windows вы не всегда используете
main()
, а скорееWinMain
илиwWinMain
, хотя вы можете использоватьmain()
, даже с набором инструментов Microsoft . В Linux можно использовать_start
.Точка входа устанавливается компоновщиком как инструментом операционной системы, а не самим языком. Вы даже можете установить нашу собственную точку входа, и вы можете сделать библиотеку, которая также будет исполняемой !
источник
main()
функцию с языком программирования C, что неверно.