Perché le macro non sono incluse nella maggior parte dei linguaggi di programmazione moderni?


31

So che sono implementati in modo estremamente pericoloso in C / C ++. Non possono essere implementati in un modo più sicuro? Gli svantaggi delle macro sono davvero abbastanza gravi da superare l'enorme potenza che forniscono?


4
Esattamente quale potere forniscono le macro che non possono essere raggiunte abbastanza facilmente in un altro modo?
Chinmay Kanchi,

2
In C #, è stata necessaria un'estensione del linguaggio di base per creare una semplice proprietà e un campo di supporto in un'unica dichiarazione. E questo richiede più di macro lessicali: come è possibile creare una funzionalità corrispondente al WithEventsmodificatore di Visual Basic ? Avresti bisogno di qualcosa come le macro semantiche.
Jeffrey Hantin,

3
Il problema con le macro è che, come tutti i potenti meccanismi, consente a un programmatore di infrangere le ipotesi che altri hanno fatto o faranno. I presupposti sono la chiave del ragionamento e senza la capacità di ragionare in modo fattibile sulla logica, fare progressi diventa proibitivo.
dan_waterworth,

2
@Chinmay: le macro generano codice. Non c'è funzionalità nel linguaggio Java con quella potenza.
Kevin Cline l'

1
@Chinmay Kanchi: le macro consentono di eseguire (valutare) il codice in fase di compilazione anziché in fase di esecuzione.
Giorgio,

Risposte:


57

Penso che il motivo principale sia che le macro sono lessicali . Ciò ha diverse conseguenze:

  • Il compilatore non ha modo di verificare che una macro sia semanticamente chiusa, ovvero che rappresenti una "unità di significato" come fa una funzione. (Considera #define TWO 1+1: cosa significa TWO*TWO? 3.)

  • Le macro non vengono digitate come le funzioni. Il compilatore non può verificare 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 per metà del tempo, oppure dovrà riportare entrambi, anche se uno probabilmente va bene. (Considera #define min(x,y) (((x)<(y))?(x):(y)): cosa dovrebbe fare il compilatore se i tipi di xe ynon corrispondono o non implementano operator<?)

  • Gli strumenti automatizzati non possono lavorare con essi in modi semanticamente utili. In particolare, non è possibile avere elementi come IntelliSense per le macro che funzionano come funzioni ma si espandono in un'espressione. (Ancora una volta, l' minesempio.)

  • Gli effetti collaterali di una macro non sono così espliciti come lo sono con le funzioni, causando potenziale confusione per il programmatore. (Considera ancora l' minesempio: in una chiamata di funzione, sai che l'espressione per xviene 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, finisci con funzioni e costanti.


32
Non tutti i linguaggi macro sono lessicali. Ad esempio, le macro Scheme sono sintattiche e i modelli C ++ sono semantici. I macrosistemi lessicali hanno la particolarità di poter essere applicati a qualsiasi linguaggio senza essere consapevoli della sua sintassi.
Jeffrey Hantin,

8
@Jeffrey: immagino che i miei "macro sono lessicali" sia davvero una scorciatoia per "quando le persone si riferiscono a macro in un linguaggio di programmazione generalmente pensano a macro lessicali". È un peccato che Scheme utilizzi questo termine caricato per qualcosa di sostanzialmente diverso. I modelli C ++, tuttavia, non sono ampiamente indicati come macro, presumibilmente precisamente perché non sono del tutto lessicali.
Timwi,

25
Penso che l'uso di Scheme del termine macro risalga a Lisp, il che probabilmente significa che precede la maggior parte degli altri usi. IIRC il sistema C / C ++ era originariamente chiamato preprocessore .
Bevan,

16
@Bevan ha ragione. Dire che le macro sono lessicali è come dire che gli uccelli non possono volare perché l'uccello con cui hai più familiarità è un pinguino. Detto questo, la maggior parte (ma non tutti) dei punti sollevati si applica anche alle macro sintattiche, anche se forse in misura minore.
Laurence Gonsalves il

13

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 del linguaggio che riscrive il 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 in modo eccessivo le macro, potresti avere grossi problemi.

    Aggiungete a ciò che molte cose semplici che potete fare con le macro (stile C / C ++) possono essere fatte in altri modi in altre lingue.

  • In altre lingue come vari dialetti Lisp, le macro sono meglio integrate con la sintassi del linguaggio principale, ma è ancora possibile riscontrare problemi con le dichiarazioni in una "perdita" di macro. Questo è affrontato da macro igieniche .


Breve contesto storico

Le macro (abbreviazione di macro-istruzioni) 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: https://stackoverflow.com/questions/3065606/when-did-the-idea-of-macros-user-defined-code -trasformazione-appaiono . http://www.csee.umbc.edu/courses/331/resources/papers/Evolution-of-Lisp.pdf . In precedenza, "fexprs" offriva funzionalità simili a macro.

Le prime versioni di C non avevano macro ( http://cm.bell-labs.com/cm/cs/who/dmr/chist.html ). Questi furono aggiunti tra il 1972 e il 1973 tramite un preprocessore. Prima di ciò, C supportava solo #includee #define.

Il macro-preprocessore M4 è nato nel 1977 circa.

Le lingue più recenti sembrano implementare macro in cui il modello operativo è 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.


9
Il C ++ è benedetto (?) Con DUE macro sistemi: il preprocessore e l'espansione del modello.
Jeffrey Hantin,

Penso che affermare che l'espansione del modello sia una macro stia allungando la definizione di macro. L'uso della tua definizione rende la domanda originale insignificante e semplicemente sbagliata, dal momento che la maggior parte dei linguaggi moderni ha delle macro (secondo la tua definizione). Tuttavia, l'uso di macro come la stragrande maggioranza degli sviluppatori lo userebbe lo rende una buona domanda.
Dunk

@Dunk, la definizione di una macro come in Lisp precede la comprensione "moderna" e stupida come nel patetico preprocessore C.
SK-logic

@SK: è meglio essere "tecnicamente" corretti e offuscare la conversazione o è meglio essere compresi?
Dunk

1
@Dunk, la terminologia non è di proprietà di orde ignoranti. La loro ignoranza non dovrebbe mai essere presa in considerazione, altrimenti porterà a smorzare l'intero dominio dell'informatica. Se qualcuno capisce "macro" solo come riferimento a un preprocessore C, non è altro che un'ignoranza. Dubito che ci sia una percentuale significativa di persone che non hanno mai sentito parlare di Lisp o persino di, pardonnez mon français, "macro" di VBA in ufficio.
SK-logic

12

Le macro possono, come osserva Scott , consentire di nascondere la logica. Naturalmente, 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, il tutto nel comfort di un unico ambiente linguistico ...

Tuttavia, può essere abusato. Può rendere il codice più difficile da leggere, comprendere e eseguire il debug, aumentare il tempo necessario ai nuovi programmatori per acquisire familiarità con una base di codice e portare a costosi errori e ritardi.

Quindi per i linguaggi destinati a semplificare la programmazione (come Java o Python), tale sistema è un anatema.


3
E poi Java è uscito dai binari aggiungendo foreach, annotazioni, asserzioni, generics by
erasure

2
@Jeffrey: e non dimentichiamoci, molti generatori di codice di terze parti . A pensarci bene, dimentichiamoli.
Shog9

4
Un altro esempio di come Java fosse semplicistico anziché semplice.
Jeffrey Hantin,

3
@JeffreyHantin: Cosa c'è che non va in foreach?
Casebash,

1
@Dunk Questa analogia potrebbe essere un tratto ... ma penso che l'estensione del linguaggio sia un po 'come il plutonio. Costoso da implementare, molto difficile da lavorare nelle forme desiderate e notevolmente pericoloso se usato in modo improprio, ma anche incredibilmente potente se applicato bene.
Jeffrey Hantin,

6

Le macro possono essere implementate in modo molto sicuro in alcune circostanze - ad esempio in Lisp, le macro sono solo funzioni che restituiscono il codice trasformato come una struttura di dati (s-espressione). Ovviamente, Lisp beneficia in modo significativo del fatto che è omoiconico e del fatto che "il codice è dati".

Un esempio di quanto possano essere semplici le macro è questo esempio Clojure che specifica un valore predefinito da utilizzare in 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 in Lisps, il consiglio generale è "non usare le macro a meno che non sia necessario".

Se non stai usando un linguaggio omoiconico, le macro diventano molto più complicate e le varie altre opzioni hanno alcune insidie:

  • Macro basate su testo - ad esempio il preprocessore C - semplici da implementare ma molto difficili da usare correttamente in quanto è necessario generare la sintassi di origine corretta in forma testuale, comprese eventuali stranezze sintattiche
  • DSLS basato su macro - ad esempio il sistema di template C ++. Complessa, può a sua volta comportare una sintassi complicata, può essere estremamente complessa per i compilatori e gli autori di strumenti da gestire correttamente poiché introduce una significativa nuova complessità nella sintassi e nella semantica del linguaggio.
  • API di manipolazione AST / bytecode - ad es. Generazione di riflessione Java / bytecode - teoricamente molto flessibili ma possono diventare molto dettagliate: può richiedere molto codice per fare cose abbastanza semplici. Se ci vogliono dieci righe di codice per generare l'equivalente di una funzione a tre righe, allora non hai guadagnato molto con i tuoi sforzi di meta-programmazione ...

Inoltre, tutto ciò che una macro può fare alla fine può essere realizzato in qualche altro modo in un linguaggio completo turing (anche se questo significa scrivere un sacco di piatti). Come risultato di tutta questa delicatezza, non sorprende che molte lingue decidano che le macro non valgono davvero la pena di implementarle.


Ugh. Non più "il codice è che i dati sono una buona cosa" immondizia. Il codice è codice, i dati sono dati e non riuscire a separare correttamente i due è un buco nella sicurezza. Penseresti che l'emergere di SQL Injection come una delle più grandi classi di vulnerabilità esistenti avrebbe screditato l'idea di rendere facilmente intercambiabili codice e dati una volta per tutte.
Mason Wheeler,

10
@ Mason - Non credo che tu capisca il concetto di codice è dati. Anche tutto il codice sorgente del programma C è costituito da dati - si trova semplicemente in formato testo. I lisps sono gli stessi, tranne per il fatto che esprimono il codice in pratiche strutture di dati intermedi (s-espressioni) che gli consentono di essere manipolato e trasformato da macro prima della compilazione. In entrambi i casi l'invio di un input non attendibile al compilatore potrebbe essere una falla di sicurezza, ma è difficile farlo accidentalmente e sarebbe colpa tua se fai qualcosa di stupido, non del compilatore.
Mikera,

La ruggine è un altro interessante sistema macro sicuro. Ha regole / semantica rigorose per evitare i tipi di problemi associati alle macro lessicali C / C ++ e utilizza un DSL per acquisire parti dell'espressione di input in variabili macro da inserire successivamente. Non è potente come la capacità di Lisps di chiamare qualsiasi funzione sull'input, ma fornisce un ampio set di utili macro di manipolazione che possono essere chiamate da altre macro.
zstewart,

Ugh. Non più immondizia di sicurezza . Altrimenti, incolpare tutti i sistemi con l'architettura von Neumann integrata, ad esempio ogni processore IA-32 con capacità di auto-modifica del codice. Ho il sospetto che tu possa riempire fisicamente il buco ... Comunque, devi affrontare il fatto in generale: l'isomorfismo tra codice e dati è la natura del mondo in diversi modi . Ed è (probabilmente) tu, il programmatore, che hai il dovere di mantenere l'invarianza dei requisiti di sicurezza, che non è lo stesso per applicare artificialmente la segregazione prematura ovunque.
FrankHB,

4

Per rispondere alle tue domande, pensa a quali macro vengono utilizzate principalmente (Attenzione: codice compilato dal cervello).

  • Le macro utilizzate per definire le costanti simboliche #define X 100

Questo può essere facilmente sostituito con: const int X = 100;

  • Le macro utilizzate per definire (essenzialmente) funzioni agnostiche di tipo inline #define max(X,Y) (X>Y?X:Y)

In qualsiasi linguaggio che supporti il ​​sovraccarico delle funzioni, questo può essere emulato in un modo molto più sicuro di tipo avendo funzioni sovraccaricate del tipo corretto o, in un linguaggio che supporta generici, da una funzione generica. La macro tenterà felicemente di confrontare qualsiasi cosa, inclusi puntatori o stringhe, che potrebbero essere compilati, ma quasi sicuramente non è quello che volevi. D'altra parte, se le macro sono state rese sicure per i tipi, non offrono vantaggi o praticità rispetto alle funzioni sovraccariche.

  • Macro utilizzate per specificare collegamenti a elementi di uso frequente. #define p printf

Questo è facilmente sostituito da una funzione p()che fa la stessa cosa. Questo è abbastanza coinvolto in C (che richiede di usare ilva_arg() famiglia di funzioni) ma in molti altri linguaggi che supportano un numero variabile di argomenti di funzioni, è molto più semplice.

Supportare queste funzionalità all'interno di una lingua 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 realtà, non riesco a pensare a un singolo caso d'uso per le macro che non può essere facilmente duplicato in un altro modo. L' unico posto in cui le macro sono veramente utili è quando sono legate a costrutti di compilazione condizionali come #if(ecc.).

Su questo punto, non discuterò con te, poiché credo che le soluzioni non preprocessore alla compilazione condizionale in linguaggi popolari siano estremamente ingombranti (come l'iniezione di bytecode in Java). Ma linguaggi come D hanno messo a punto soluzioni che non richiedono un preprocessore e non sono più ingombranti rispetto all'utilizzo dei condizionali del preprocessore, pur essendo molto meno soggetti a errori.


1
se devi #define max, inserisci parentesi attorno ai parametri, quindi non ci sono effetti imprevisti dalla precedenza dell'operatore ... come #define max (X, Y) ((X)> (Y)? (X) :( Y))
foo

Ti rendi conto che era solo un esempio ... L'intento era di illustrare.
Chinmay Kanchi,

+1 per questa sistematica gestione della questione. Mi piace aggiungere che la compilazione condizionale può facilmente diventare un incubo - Ricordo che esisteva un programma chiamato "unifdef" (??) il cui scopo era quello di rendere visibile ciò che rimaneva dopo il postprocessing.
Ingo,

7
In fact, I can't think of a single use-case for macros that can't easily be duplicated in another way: almeno in C, non è possibile formare identificatori (nomi di variabili) utilizzando la concatenazione di token senza utilizzare le macro.
Charles Salvia,

Le macro consentono di garantire che determinate strutture di codice e dati rimangano "parallele". Ad esempio, se ci sono un numero limitato di condizioni con messaggi associati e sarà necessario persistere in un formato conciso, tentare di utilizzare un enumper definire le condizioni e un array di stringhe costante per definire i messaggi potrebbe causare problemi se il enum e l'array non sono sincronizzati. L'uso di una macro per definire tutte le coppie (enum, string) e quindi l'inclusione di quella macro due volte con altre definizioni adeguate nell'ambito ogni volta consentirebbe di mettere ciascun valore enum accanto alla sua stringa.
supercat

2

Il problema più grande che ho riscontrato con le macro è che quando sono molto usati possono rendere il codice molto difficile da leggere e mantenere poiché ti permettono di nascondere nella macro una logica che può essere o meno facile da trovare (e che può essere o meno banale ).


La macro non dovrebbe essere documentata?
Casebash,

@Casebash: Certo, la macro dovrebbe essere documentata come qualsiasi altro codice sorgente ... ma in pratica l'ho visto raramente fatto.
Scott Dorman,

3
Hai mai eseguito il debug della documentazione? Non è sufficiente quando si tratta di codice scritto male.
JeffO,

OOP ha anche alcuni di questi problemi ...
aoeu256,

2

Per cominciare, notiamo che i MACRO in C / C ++ sono molto limitati, soggetti a errori e non molto utili.

I MACRO implementati nel linguaggio dell'assemblatore LISP o z / OS possono essere affidabili e incredibilmente utili.

Ma a causa dell'abuso della funzionalità limitata in C hanno una cattiva reputazione acquisita. Quindi nessuno implementa più le macro, invece ottieni cose come i modelli che fanno alcune delle semplici cose che le macro erano solite fare e cose come le annotazioni di Java che fanno alcune delle cose più complesse che le macro sono solite fare.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.