#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 Стандартної бібліотеки . Сама С не обмежує цього.
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()!
Власне кажучи, ви можете мати програму на С і мати змогу скомпілювати її, не маючи 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'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 Наприклад, ви можете мати наступне як дуже дійсну програму на С, що змушує компілятор скуголити лише тоді, коли ви піднімаєте рівень попереджень.
/* 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частини визначеного стандарту, чи це лише специфіка реалізації? Звичайно, ваш "основний як масив" є специфічним для архітектури. Також важливо, що не буде нерозумним, щоб трюк "основний як масив" зазнав збою під час виконання через обмеження безпеки (хоча це було б більш імовірно, якби ви не використовували 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прапором. Це зупинить процес компіляції після попередньої обробки, і ви побачите результат вставки маркера.
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()функцію з мовою програмування С, що є неправильним.