#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?? ");
}
Czy to pośrednio dzwoni main
? w jaki sposób?
#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?? ");
}
Czy to pośrednio dzwoni main
? w jaki sposób?
Odpowiedzi:
Język C definiuje środowisko wykonawcze w dwóch kategoriach: wolnostojące i hostowane . W obu środowiskach wykonawczych środowisko wywołuje funkcję w celu uruchomienia programu.
W środowisku wolnostojącym funkcję uruchamiania programu można zdefiniować za pomocą implementacji, podczas gdy w środowisku hostowanym tak powinno być main
. Żaden program w języku C nie może działać bez funkcji uruchamiania programu w określonych środowiskach.
W twoim przypadku main
jest ukryty przez definicje preprocesora. begin()
rozszerzy się, do decode(a,n,i,m,a,t,e)
którego dalej zostanie rozszerzony 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)
to sparametryzowane makro z 7 parametrami. Lista zastępcza tego makra to m##s##u##t
. m, s, u
i t
są 4 p , 1 st , 3 rd i 2 ND parametr stosowany na liście zastępczej.
s, t, u, m, p, e, d
1 2 3 4 5 6 7
Reszta na nic się nie przyda ( tylko do zaciemniania ). Przekazany argument decode
to „ a , n , i , m , a, t, e”, więc identyfikatory m, s, u
i t
są zastępowane odpowiednio argumentami m, a, i
i n
.
m --> m
s --> a
u --> i
t --> n
_start()
. Lub nawet na niższym poziomie mogę spróbować po prostu wyrównać początek mojego programu z adresem, na który ustawiany jest adres IP po uruchomieniu. main()
jest biblioteką C Standard . Sam C nie nakłada na to ograniczeń.
decode(a,n,i,m,a,t,e)
się stało m##a##i##n
? Czy zastępuje znaki? Czy możesz podać link do dokumentacji decode
funkcji? Dzięki.
begin
jest zdefiniowany do zastąpienia, przez decode(a,n,i,m,a,t,e)
który zdefiniowano wcześniej. Ta funkcja pobiera argumenty s,t,u,m,p,e,d
i łączy je w tej formie m##s##u##t
( ##
oznacza konkatenację). Tzn. Ignoruje wartości p, e i d. A ty "połączenia" decode
z y = A, T = N, U = l, m = M skutecznie zastępuje begin
się main
.
Spróbuj użyć gcc -E source.c
, wyjście kończy się na:
int main()
{
printf("Ha HA see how it is?? ");
}
Zatem main()
funkcja jest faktycznie generowana przez preprocesor.
Program, o którym mowa , wywołuje z main()
powodu rozszerzenia makr, ale twoje założenie jest błędne - wcale nie musi wywoływać main()
!
Ściśle mówiąc, możesz mieć program w C i być w stanie skompilować go bez main
symbolu. main
jest czymś, do czego c library
oczekuje skok po zakończeniu własnej inicjalizacji. Zwykle skaczesz main
z symbolu libc znanego jako _start
. Zawsze można mieć bardzo poprawny program, który po prostu wykonuje asemblację, bez posiadania pliku main. Spójrz na to:
/* 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 */
}
Skompiluj powyższe z gcc -nostdlib without_main.c
i zobacz, jak wyświetla się Hello World!
na ekranie, po prostu wywołując wywołania systemowe (przerwania) w asemblerze wbudowanym.
Aby uzyskać więcej informacji na temat tego konkretnego problemu, odwiedź blog ksplice
Inną interesującą kwestią jest to, że możesz również mieć program, który kompiluje się bez main
odpowiadania symbolowi funkcji C. Na przykład możesz mieć następujący program jako bardzo poprawny program w C, który spowoduje, że kompilator będzie jęczeć tylko po podniesieniu poziomu Ostrzeżenia.
/* 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
};
Wartości w tablicy to bajty, które odpowiadają instrukcjom potrzebnym do wydrukowania Hello World na ekranie. Aby uzyskać bardziej szczegółowe informacje o tym, jak działa ten konkretny program, spójrz na ten wpis na blogu , w którym również go przeczytałem.
Chcę jeszcze raz zwrócić uwagę na te programy. Nie wiem, czy rejestrują się jako prawidłowe programy w C zgodnie ze specyfikacją języka C, ale ich kompilacja i uruchamianie jest z pewnością bardzo możliwe, nawet jeśli naruszają samą specyfikację.
_start
częścią zdefiniowanego standardu, czy jest to tylko specyficzna dla implementacji? Z pewnością twój "main jako tablica" jest specyficzny dla architektury. Co więcej, nie byłoby nierozsądne, gdyby sztuczka „główna jako tablica” zawodziła w czasie wykonywania z powodu ograniczeń bezpieczeństwa (chociaż byłoby to bardziej prawdopodobne, gdybyś nie użył const
kwalifikatora, a wiele systemów by na to pozwalało).
_start
nie jest w standardzie ELF, że 64-bitową psABI zawiera odniesienie do _start
co 3,4 proces inicjalizacji . Oficjalnie ELF wie tylko o adresie e_entry
w nagłówku ELF, _start
to tylko nazwa wybrana przez implementację.
const
to nie ma znaczenia - nazwa symbolu w tym binarnym pliku wykonywalnym to main
. Nie więcej nie mniej. const
jest konstrukcją C, która nic nie znaczy w czasie wykonywania.
Ktoś próbuje zachowywać się jak Mag. Myśli, że może nas oszukać. Ale wszyscy wiemy, że wykonanie programu c zaczyna się od main()
.
int begin()
Zostaną zastąpione decode(a,n,i,m,a,t,e)
jednym przejściu etapu preprocesora. Z drugiej strony decode(a,n,i,m,a,t,e)
zostanie zastąpiony przez m ## a ## i ## n. Podobnie jak w przypadku pozycyjnego skojarzenia wywołania makra, s
will ma wartość znaku a
. Podobnie u
zostanie zastąpione przez „i” i t
zastąpione przez „n”. I tak m##s##u##t
się staniemain
Jeśli chodzi o ##
symbol w rozwijaniu makra, jest to operator przetwarzania wstępnego i wykonuje wklejanie tokenu. Kiedy makro jest rozwinięte, dwa tokeny po obu stronach każdego operatora „##” są łączone w jeden token, który następnie zastępuje „##” i dwa oryginalne tokeny w rozwinięciu makra.
Jeśli mi nie wierzysz, możesz skompilować swój kod z -E
flagą. Zatrzyma proces kompilacji po wstępnym przetwarzaniu i możesz zobaczyć wynik wklejania tokenu.
gcc -E FILENAME.c
decode(a,b,c,d,[...])
tasuje pierwsze cztery argumenty i łączy je w celu uzyskania nowego identyfikatora w określonej kolejności dacb
. (Pozostałe trzy argumenty są ignorowane.) Na przykład decode(a,n,i,m,[...])
podaje identyfikator main
. Zwróć uwagę, że właśnie begin
tak zdefiniowano makro.
Dlatego begin
makro jest po prostu zdefiniowane jako main
.
W twoim przykładzie main()
funkcja jest faktycznie obecna, ponieważ begin
jest makrem, które kompilator zastępuje decode
makrem, które z kolei zastępuje wyrażeniem m ## s ## u ## t. Używając rozszerzenia makro ##
, dotrzesz do słowa main
z decode
. To jest ślad:
begin --> decode(a,n,i,m,a,t,e) --> m##parameter1##parameter3##parameter2 ---> main
To tylko sztuczka main()
, ale używanie nazwy main()
funkcji wejścia programu nie jest konieczne w języku programowania C. Zależy to od systemów operacyjnych i konsolidatora jako jednego z jego narzędzi.
W systemie Windows nie zawsze używasz main()
, ale raczej WinMain
lubwWinMain
, chociaż możesz main()
, nawet z łańcuchem narzędzi Microsoft . W Linuksie można użyć _start
.
To zależy od konsolidatora jako narzędzia systemu operacyjnego, aby ustawić punkt wejścia, a nie sam język. Możesz nawet ustawić własny punkt wejścia i możesz stworzyć bibliotekę, która jest również wykonywalna !
main()
funkcję z językiem programowania C, co nie jest poprawne.