Newsgroup: it.comp.lang.c
Titolo: Preprocessore del C: come funziona?

Come e' implementato il preprocessore del C?
Inizialmente pensavo che si limitasse a sostituire nel testo successivo ad
ogni riga con la direttiva #define il nome della costante o della macro con
la sua definizione, ma poi ho provato a eseguire:
#define MACRO(a,b)  (  ((a) > (b)) ? (a) : (b) )  /* calcola il massimo deli
argomenti */
#define MACRO(a,b)  (  ((a) < (b)) ? (a) : (b) )  /* calcola il minimo degli
argomenti */
main () {
   printf ( "%d", MACRO(3, 7));
}
Se il preprocessore funzionasse nel modo suddetto sostituendo con la
definizione della prima riga, la seconda riga diverrebbe:
#define (  ((a) > (b)) ? (a) : (b) )   (  ((a) < (b)) ? (a) : (b) )
che darebbe messaggio d' errore in fase di complilazione.
Invece il programma si comporta come da aspettativa, stampando il numero 3,
sostituendo alla prima definizione della macro la seconda.
D'altronde se il preprocessore semplicemente ignorasse le righe con la
direttiva #define allora l'istruzione nella main diverrebbe:
   printf ( "%d", (  ((3) > (7)) ? (3): (7) ) );
onde la ridefinizione della macro nella seconda riga non avrebbe effetto.
Ho fatto altre ipotesi sul funzionamento del preprocessore e ogni volta ho
trovato delle ragioni per cui non potevano essere esatte: tuttavia mi sembra
ozioso riportarle qui, oltreche' prolisso.
La domanda e' dettata non solo da curiosita' ma anche perche' mi serviva per
un mio progetto un preprocessore di macro e ho pensato di rifarmi a quello
del C.
Non mi sorprenderei se l'implementazione fosse poi relativamente semplice,
mentre le soluzioni che ho immaginato mi sembrano troppo complesse.
Saluti a tutto il NG.
--
In teoria la  pratica e la teoria sono la stessa cosa, in pratica no.


fin qui mi sembra giusto.
penso che il funzionamento sia molto semplice:
la riga comincia con # allora e' un comando per preprocessore -> analisi del
comando
non comincia con # allora se trova un simbolo a lui conosciuto lo espande
(nota che il preprocessore deve conoscere la sintassi del linguaggio
utilizzato, in C non c'e' espansione dei simboli se fanno parte di una
stringa o di un commento)
nel tuo caso il comando define definisce due volte lo stesso simbolo, il
preprocessore buttavia la definizione precedente e la sostituisce con la
nuova (con il BC3.1 hai un warning comunque).
ciao
donCiccio

La #define funziona cosi':
regola 1 (sostituzione grezza):
per ogni forma del tipo 
   #define ID X 
dove X puo' essere niente, oppure una qualunque frase ogni volta che
viene trovato nel codice ID viene sostituito con X.
Ma questa l'avevi intuita :)
regola 2 (sostituzione elaborata):
per ogni forma del tipo
    #define ID(parlist) X
sostituisce a ogni occorrenza di ID(current_parlist) il contenuto di
X, operando pero' una modifica:
- per ogni elemento contenuto in X appartenente in parlist viene
sostituito il corrispondente elemento presente in current_parlist.
Esempio:
    #define pippo(a, b) a+b
    /* ricordo che
chiamato con:
    int v1 = 10, res = 12; 
    pippo(v1, res);
sostituisce pippo(v1, res) con:
    v1+res;
Nota che il ";" lo mette perche' era presente nella chiamata pippo(v1,
res).
In piu' nelle define puoi usare l'operatore ## per concatenare 2
stringhe.
Es. (prova a indovinarne l'uso :) )
    #define new(klass) klass##_new()
codice:
    new(pippo);
diventa:
    pippo_new();
Mox :)

 Come e' implementato il preprocessore del C?
Il preprocessore, in C, puo' assolvere a tre compiti
specifici:
1. Inclusione di file.
Si usa il comando #include con due forme diverse:
#include "nomefile.h"
oppure:
#include <nomefile.h>
Nel primo caso il preprocessore cerca il file da
includere nella cartella corrente, nel secondo
nelle cartelle standard.
La seconda forma, quindi, si usa quasi sempre
per header standard.
------------------------------------------------
2. Definzione di macro e sostituzione.
Si usa il comando #define e l'identificatore 
seguente il comando puo' accettare dei parametri:
#define pippo   5
oppure:
#define pippo(a,b)  ((a)+(b))
Ogni volta che il preprocessore incontra, nel
codice C, l'identificatore "pippo", lo sostituisce
con il valore associato.
------------------------------------------------
3. Compilazione condizionale.
Si usano i comandi #ifdef/#ifndef con una struttura
del tipo:
#ifdef MICROSOFT
   #include <conio.h>
#else
   #include <stdio.h>
#endif
Il preprocessore, cioe', segue due strade diverse,
se e' definito o meno l'identificatore MICROSOFT.
Stesso discorso per #ifndef (dove la n sta per not).
-- 
Posted via Mailgate.ORG Server - 
http://www.Mailgate.ORG

nuova (con il BC3.1 hai un warning comunque).
Bella li'!
In effetti ero finito in un loop perche' (misteri della mente umana) pensavo
che dopo ogni direttiva il preprocessore modificasse il file originale, e
quindi riprendesse l'analisi dal corrispondente punto dopo la direttiva (con
sommo dispendio di tempo).
Tutto funziona liscio invece se si suppone che il preprocessore agisca
secondo l'algoritmo:
leggi riga (o istruzione, oppure stringa delimitata dai caratteri,
oppure...);
se definizione di macro {
   se la macro e'gia' definita butta via la vecchia definizione;
   poni in una struttura dati con la sua definizione (e.g. lista concatenata
      oppure albero );
}
altrimenti preprocessa la riga (oppure...) e copia nel nuovo file la riga
preprocessata;
Per quanto riguarda quest'ultimo passaggio, si puo' ipotizzare che funzioni
cosi':
quando non hai controllato tutti i nomi di macro {
   cerca nella riga un nome di macro;
   se trovi  {
      sostituisci;
      segna la parte della struttura contenente la macro in modo che non
venga
         considerata piu' nel processo /* altrimenti: vedi dopo */
      ripeti la procedura alla riga sostituita;
      esci dal ciclo;
   }
   altrimenti passa al nome di macro successivo;
}
La ricorsivita' e' necessaria perche' possa preprocessare correttamente
righe che contengano macro che contengaono a loro volte nomi di macro (cosi'
si comporta il preprocessore del C!).
D'altronde se non segnasse la macro come gia' sostituita si avrebbero
situazioni del tipo:
#define PIPPO(a) PIPPO(a) +1
...
int numero = 0;
PIPPO(numero);
PIPPO(numero) diverrebbe al primo passaggio PIPPO(0) +1;
al secondo passaggio PIPPO(0) +1 +1;
...
PIPPO(0) +1 +1 ... +1
mandando a un certo punto in crash il preprocessore.
 Ciao.