#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. Когда вы «называете» decodes = 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
Что касается ##символа в раскрытии макроса, это оператор предварительной обработки, который выполняет вставку токена. Когда макрос раскрывается, два токена по обе стороны от каждого оператора '##' объединяются в один токен, который затем заменяет '##' и два исходных токена в раскрытии макроса.
Если вы мне не верите, вы можете скомпилировать свой код с помощью -Eflag. Он остановит процесс компиляции после предварительной обработки, и вы сможете увидеть результат вставки токена.
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, что неверно.