#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()
функцію з мовою програмування С, що є неправильним.