#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- м , 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
, соответственно.
m --> m
s --> a
u --> i
t --> 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. Он остановит процесс компиляции после предварительной обработки, и вы сможете увидеть результат вставки токена.
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
.
Точка входа устанавливается компоновщиком как инструментом операционной системы, а не самим языком. Вы даже можете установить нашу собственную точку входа, и вы можете сделать библиотеку, которая также будет исполняемой !
main()
функцию с языком программирования C, что неверно.