#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?? ");
}
Questo chiama indirettamente main
? Come?
#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?? ");
}
Questo chiama indirettamente main
? Come?
Risposte:
Il linguaggio C definisce l'ambiente di esecuzione in due categorie: indipendente e ospitato . In entrambi gli ambienti di esecuzione una funzione viene chiamata dall'ambiente per l'avvio del programma.
In un ambiente indipendente , la funzione di avvio del programma può essere definita implementazione mentre in un ambiente ospitato dovrebbe essere main
. Nessun programma in C può essere eseguito senza la funzione di avvio del programma negli ambienti definiti.
Nel tuo caso, main
è nascosto dalle definizioni del preprocessore.begin()
si espanderà a decode(a,n,i,m,a,t,e)
cui verrà ulteriormente espanso 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)
è una macro parametrizzata con 7 parametri. L'elenco di sostituzione per questa macro è m##s##u##t
. m, s, u
e t
sono 4 ° , 1 ° , 3 ° e 2 ° parametro utilizzato nella lista di sostituzione.
s, t, u, m, p, e, d
1 2 3 4 5 6 7
Il resto non serve ( solo per offuscare ). L'argomento passato a decode
è " a , n , i , m , a, t, e" quindi, gli identificatori m, s, u
e t
vengono sostituiti con argomenti m, a, i
e n
, rispettivamente.
m --> m
s --> a
u --> i
t --> n
_start()
. O ancora più di basso livello posso provare ad allineare semplicemente l'inizio del mio programma con l'indirizzo a cui è impostato l'IP dopo l'avvio. main()
è C standard biblioteca . C stesso non impone restrizioni su questo.
decode(a,n,i,m,a,t,e)
diventa m##a##i##n
? Sostituisce i personaggi? Potete fornire un collegamento alla documentazione della decode
funzione? Grazie.
begin
è definito per essere sostituito da quello decode(a,n,i,m,a,t,e)
definito prima. Questa funzione prende gli argomenti s,t,u,m,p,e,d
e li concatena in questa forma m##s##u##t
( ##
significa concatenare). Cioè, ignora i valori di p, e e d. Quando "chiami" decode
con s = a, t = n, u = i, m = m, effettivamente si sostituisce begin
con main
.
Prova a utilizzare gcc -E source.c
, l'output termina con:
int main()
{
printf("Ha HA see how it is?? ");
}
Quindi una main()
funzione è effettivamente generata dal preprocessore.
Il programma in questione fa chiamata main()
a causa di espansione di macro, ma la tua ipotesi è difettoso - è non dover chiamare main()
a tutti!
A rigor di termini, puoi avere un programma C ed essere in grado di compilarlo senza avere un main
simbolo. main
è qualcosa a cui si c library
aspetta di passare, dopo aver terminato la propria inizializzazione. Di solito si salta main
dal simbolo libc noto come _start
. È sempre possibile avere un programma molto valido, che esegue semplicemente assembly, senza avere un main. Guarda questo:
/* 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 */
}
Compilare quanto sopra con gcc -nostdlib without_main.c
e vederlo in stampaHello World!
sullo schermo semplicemente emettendo chiamate di sistema (interrupt) in assembly inline.
Per ulteriori informazioni su questo particolare problema, controlla il blog di ksplice
Un altro problema interessante è che puoi anche avere un programma che si compila senza che il main
simbolo corrisponda a una funzione C. Ad esempio, puoi avere quanto segue come programma C molto valido, che fa lamentare il compilatore solo quando si sale al livello di avvertenza.
/* 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
};
I valori nell'array sono byte che corrispondono alle istruzioni necessarie per stampare Hello World sullo schermo. Per un resoconto più dettagliato di come funziona questo programma specifico, dai un'occhiata a questo post del blog , dove l'ho letto per primo.
Voglio fare un ultimo avviso su questi programmi. Non so se si registrano come programmi C validi secondo la specifica del linguaggio C, ma compilarli ed eseguirli è certamente molto possibile, anche se violano la specifica stessa.
_start
parte di uno standard definito o è solo specifico dell'implementazione? Certamente il tuo "main as an array" è specifico dell'architettura. Inoltre, non sarebbe irragionevole che il trucco "main as an array" fallisse in fase di esecuzione a causa di restrizioni di sicurezza (sebbene ciò sarebbe più probabile se non si usasse il const
qualificatore, e molti sistemi lo consentirebbero).
_start
non è nello standard ELF, sebbene la psABI AMD64 contenga un riferimento _start
a 3.4 Inizializzazione del processo . Ufficialmente, ELF conosce solo l'indirizzo e_entry
nell'intestazione ELF, _start
è solo un nome scelto dall'implementazione.
const
non importa un bit: il nome del simbolo in quel file eseguibile binario è main
. Ne più ne meno. const
è un costrutto C che non significa nulla al momento dell'esecuzione.
Qualcuno sta cercando di comportarsi come un mago. Pensa di poterci ingannare. Ma sappiamo tutti, l'esecuzione del programma c inizia con main()
.
Il int begin()
sarà sostituito decode(a,n,i,m,a,t,e)
da un passaggio di fase di preprocessore. Poi di nuovo, decode(a,n,i,m,a,t,e)
sarà sostituito con m ## a ## i ## n. Come per l'associazione posizionale della chiamata macro, s
will ha un valore di carattere a
. Allo stesso modo, u
sarà sostituito da "i" e t
sarà sostituito da "n". Ed è così m##s##u##t
che diventeràmain
Per quanto riguarda, ##
simbolo in espansione macro, è l'operatore di preelaborazione ed esegue il token paste. Quando una macro viene espansa, i due token su entrambi i lati di ciascun operatore "##" vengono combinati in un unico token, che quindi sostituisce "##" e i due token originali nell'espansione della macro.
Se non mi credi, puoi compilare il tuo codice con -E
flag. Fermerà il processo di compilazione dopo la preelaborazione e potrai vedere il risultato dell'incollaggio del token.
gcc -E FILENAME.c
decode(a,b,c,d,[...])
mescola i primi quattro argomenti e li unisce per ottenere un nuovo identificatore, nell'ordine dacb
. (Gli altri tre argomenti vengono ignorati.) Ad esempio, decode(a,n,i,m,[...])
fornisce l'identificatore main
. Nota che questo è ciò come begin
viene definita la macro.
Pertanto, la begin
macro è semplicemente definita come main
.
Nel tuo esempio, la main()
funzione è effettivamente presente, perché begin
è una macro che il compilatore sostituisce con una decode
macro che a sua volta sostituita dall'espressione m ## s ## u ## t. Usando l'espansione macro ##
, raggiungerai la parola main
da decode
. Questa è una traccia:
begin --> decode(a,n,i,m,a,t,e) --> m##parameter1##parameter3##parameter2 ---> main
È solo un trucco da avere main()
, ma l'uso del nome main()
per la funzione di immissione del programma non è necessario nel linguaggio di programmazione C. Dipende dai tuoi sistemi operativi e dal linker come uno dei suoi strumenti.
In Windows, non si usa sempre main()
, ma piuttosto WinMain
owWinMain
, sebbene sia possibile utilizzare main()
, anche con la toolchain di Microsoft . In Linux, si può usare _start
.
Spetta al linker come strumento del sistema operativo impostare il punto di ingresso e non la lingua stessa. Puoi persino impostare il nostro punto di ingresso e puoi creare una libreria che sia anche eseguibile !
main()
funzione al linguaggio di programmazione C, che non è corretto.