So che sono implementati in modo estremamente infondato in C / C ++. Non possono essere implementati in un modo più sicuro? Gli svantaggi dei macro sono davvero così gravi da superare l'enorme potenza che forniscono?
Penso che la ragione principale sia che le macro sono lessicali . Questo ha diverse conseguenze:
Il compilatore non ha modo di verificare che una macro sia semanticamente chiusa, cioè che rappresenti una "unità di significato" come fa una funzione. (Considera #define TWO 1+1
- cosa significa TWO*TWO
uguale? 3.)
I macro non vengono digitati come le funzioni. Il compilatore non può controllare che i parametri e il tipo restituito abbiano un senso. Può solo controllare l'espressione espansa che utilizza la macro.
Se il codice non viene compilato, il compilatore non ha modo di sapere se l'errore si trova nella macro stessa o nel luogo in cui viene utilizzata la macro. Il compilatore segnalerà il posto sbagliato la metà del tempo, oppure dovrà riportare entrambi, anche se probabilmente uno di essi va bene. (Considera #define min(x,y) (((x)<(y))?(x):(y))
: cosa dovrebbe fare il compilatore se i tipi di x
e y
non corrispondono o non implementano operator<
?)
Gli strumenti automatici non possono lavorare con loro in modi semanticamente utili. In particolare, non puoi avere cose come IntelliSense per macro che funzionano come funzioni ma espandono un'espressione. (Di nuovo, l'esempio min
.)
Gli effetti collaterali di una macro non sono così espliciti come sono con le funzioni, causando una potenziale confusione per il programmatore. (Prendi di nuovo l'esempio min
: in una chiamata di funzione, sai che l'espressione per x
viene valutata solo una volta, ma qui non puoi sapere senza guardare la macro.)
Come ho detto, queste sono tutte conseguenze del fatto che le macro sono lessicali. Quando provi a trasformarli in qualcosa di più appropriato, ti ritroverai con funzioni e costanti.
Ma sì, le macro possono essere progettate e implementate meglio che in C / C ++.
Il problema con le macro è che sono effettivamente un meccanismo di estensione della sintassi della lingua che riscrive il tuo codice in qualcos'altro.
Nel caso C / C ++, non esiste un controllo di integrità fondamentale. Se stai attento, le cose vanno bene. Se commetti un errore o se usi un uso eccessivo dei macro, puoi avere grossi problemi.
Aggiungi a questo che molte cose semplici che puoi fare con i macro (stile C / C ++) possono essere fatte in altri modi in altre lingue.
In altre lingue, come i vari dialetti Lisp, i macro sono meglio integrati con la sintassi del linguaggio principale, ma puoi ancora avere problemi con le dichiarazioni in una "perdita" di macro. Questo è indirizzato da macro igieniche .
Le macro (abbreviazione di macroistruzione) sono apparse per la prima volta nel contesto del linguaggio assembly. Secondo Wikipedia , le macro erano disponibili in alcuni assemblatori IBM negli anni '50.
Il LISP originale non aveva macro, ma furono introdotte per la prima volta in MacLisp a metà degli anni '60: link . link . In precedenza, "fexprs" offriva funzionalità di tipo macro.
Le prime versioni di C non avevano macro ( link ) . Questi sono stati aggiunti circa 1972-73 tramite un preprocessore. In precedenza, C supportava solo #include
e #define
.
Il preprocessore macro M4 è nato nel 1977 circa.
Le lingue più recenti sembrano implementare macro in cui il modello di operazione è sintattico piuttosto che testuale.
Quindi, quando qualcuno parla del primato di una particolare definizione del termine "macro", è importante notare che il significato si è evoluto nel tempo.
Le macro possono, as Scott note , ti consentono di nascondere la logica. Ovviamente, anche funzioni, classi, librerie e molti altri dispositivi comuni.
Ma un potente sistema macro può andare oltre, permettendoti di progettare e utilizzare sintassi e strutture che normalmente non si trovano nella lingua. Questo può essere davvero uno strumento meraviglioso: linguaggi specifici del dominio, generatori di codice e altro ancora, tutto nel comfort di un unico ambiente linguistico ...
Tuttavia, può essere abusato. Può rendere più difficile la lettura, la comprensione e il debug del codice, aumentare il tempo necessario affinché i nuovi programmatori acquisiscano familiarità con un codebase e causare errori e ritardi costosi.
Quindi per le lingue destinate alla programmazione semplificazione (come Java o Python), un tale sistema è un anatema.
Le macro possono essere implementate in modo sicuro in alcune circostanze: in Lisp, ad esempio, le macro sono solo funzioni che restituiscono il codice trasformato come una struttura di dati (espressione s). Naturalmente, Lisp beneficia in modo significativo del fatto che è omoiconico e che "il codice è dati".
Un esempio di quanto semplici possano essere le macro è questo esempio di Clojure che specifica un valore predefinito da utilizzare nel caso di un'eccezione:
(defmacro on-error [default-value code]
'(try ~code (catch Exception ~'e ~default-value)))
(on-error 0 (+ nil nil)) ;; would normally throw NullPointerException
=> 0 ;l; but we get the default value
Anche se in Lisps, il consiglio generale è "non usare macro a meno che non sia necessario".
Se non stai utilizzando un linguaggio omoiconico, le macro diventano molto più complicate e tutte le altre opzioni presentano alcune insidie:
Inoltre, tutto ciò che una macro può fare può essere raggiunto in un altro modo in un linguaggio completo (anche se questo significa scrivere molto standard). Come risultato di tutto questo inganno, non sorprende che molte lingue decidano che le macro non valgono davvero la pena di implementare.
Per rispondere alle tue domande, pensa a quali macro vengono utilizzate prevalentemente per (Attenzione: codice compilato dal cervello).
#define X 100
Questo può essere facilmente sostituito con: const int X = 100;
#define max(X,Y) (X>Y?X:Y)
In qualsiasi linguaggio che supporti l'overloading delle funzioni, questo può essere emulato in un modo molto più sicuro per tipo avendo funzioni sovraccariche del tipo corretto o, in un linguaggio che supporta i generici, da una funzione generica. La macro tenterà felicemente di confrontare qualsiasi cosa compresi puntatori o stringhe, che potrebbero essere compilati, ma quasi certamente non è quello che volevi. D'altra parte, se hai reso sicuri i tipi di macro, non offrono vantaggi o convenienza rispetto alle funzioni sovraccaricate.
Questo è facilmente sostituito da una funzione #define p printf
che fa la stessa cosa. Questo è abbastanza coinvolto in C (che richiede di usare la famiglia di funzioni p()
) ma in molti altri linguaggi che supportano numeri variabili di argomenti di funzione, è molto più semplice.
Il supporto di queste funzionalità all'interno di un linguaggio piuttosto che in un linguaggio macro speciale è più semplice, meno soggetto a errori e molto meno confuso per gli altri che leggono il codice. In effetti, non riesco a pensare a un caso d'uso singolo per macro che non può essere duplicato facilmente in un altro modo. Il posto solo in cui le macro sono veramente utili è quando sono legate a costrutti di compilazione condizionale come va_arg()
(ecc.).
Su questo punto, non discuterò con voi, dal momento che ritengo che le soluzioni non preprocessuali alla compilazione condizionale nei linguaggi popolari siano estremamente macchinose (come l'iniezione bytecode in Java). Ma linguaggi come D hanno messo a punto soluzioni che non richiedono un preprocessore e non sono più macchinose dell'uso dei condizionali del preprocessore, pur essendo molto meno soggetta a errori.
Il problema più grande che ho riscontrato con i macro è che quando vengono utilizzati intensamente possono rendere il codice molto difficile da leggere e mantenere poiché consentono di nascondere la logica nella macro che può essere o non essere facile da trovare (e potrebbe non essere banale).
Per cominciare, si noti che i MACRO in C / C ++ sono molto limitati, soggetti a errori e non proprio utili.
MACRO come implementato in LISP, o il linguaggio assemblatore z / OS può essere affidabile e incredibilmente utile.
Ma a causa dell'abuso delle funzionalità limitate in C hanno una cattiva reputazione. Quindi nessuno implementa più le macro, invece si ottengono cose come i Templates che eseguono alcune delle semplici cose che le macro sono solite fare e cose come le annotazioni di Java che fanno alcune delle cose più complesse che le macro sono abituate a fare.
Leggi altre domande sui tag programming-languages macros