E’ il primo pensiero che m’è venuto in mente quando ho iniziato a leggere il commento di un programmatore Amiga nel più famoso e frequentato forum internazionale della gloriosa macchina (EAB. Fonte d’informazione nonché d’ispirazione per diversi articoli), il quale ha condensato in poche righe una serie di leggende metropolitane e sciocchezze da antologia, che riporto brevemente per comodità:
Asm is the best coding school. Once you master it, you are a better coder overall because you know how things work.
It provides more coding freedom. You do whatever you want.
Obviously your code will be shorter and faster.
In asm you can call any function located anywhere in the source, without prior declaration. Last time i’ve looked, in C you had to either pay attention to order, or define same thing several times.
C syntax is simply horrible
In asm you can have nice, aligned, one-comment per code line.
Unlike C, in asm you will not run into undefined behaviours without even knowing it because the spec is so overcomplicated and nobody knows it fully.
Asm is the best coding school
Diciamo che se siete masochisti autolesionisti allora l’assembly (abbreviato spesso con asm) fa certamente al caso vostro come linguaggio d’elezione, poiché la quantità di sbattimenti e sforzi necessari nonché mal di testa provocati per poter arrivare a qualcosa di funzionante non ha pari rispetto a tutti gli altri linguaggi di programmazione (a parte il linguaggio macchina. Oppure roba “psichedelica”, come Brainfuck o Whitespace che, comunque, nascono più che altro per gioco / goliardia).
Infatti se c’è qualcosa in cui eccelle questo linguaggio è quello di farsi dannatamente male nell’usarlo. In questo, effettivamente, rappresenta la migliore scuola: quella in cui si sacrifica la sanità mentale (e corporea, considerata l’enorme quantità di tempo che è necessario spendere davanti a monitor e tastiera per arrivare a qualcosa che con altri linguaggi si ottiene anche in un centesimo del tempo) sull’altare del puro appagamento edonistico.
Appagamento, questo, generato dall’autoconvizione (perché si tratta di un film riprodotto soltanto nella testa di chi se n’è convinto) che l’assembly sia il linguaggio più fico in quanto “di più basso livello” (vedi anche la prossima sezione) e, quindi, per una perversa quanto esotica “logica” allora chi ci programma diviene automaticamente fico a sua volta e, ovviamente, degno di ammirazione e stima.
Che è un po’ come dire che pescare pesci a mani nude fa di te un super fico per eccellenza. Volete mettere con l’uso di una banale nonché ordinaria canna da pesca con tanto di esca all’amo? Troppa comodità! E per la caccia, ovviamente, non possiamo che usare il fido strumento preso in prestito dalla natura:
A quanto pare decenni di ricerca, sviluppo, e proficuo utilizzo nell’ambito dei linguaggi di programmazione non hanno insegnato niente. Anzi, forse sono visti come un’inutile perdita di tempo quando c’era già il non plus ultra a disposizione.
Fermo restando che non passa minimamente per la testa che la cosa più importante per un programmatore e/o ingegnere del software dovrebbe essere quella di risolvere problemi nel “migliore dei modi” (miglior compromesso possibile in base ai requisiti e al contesto applicativo), per cui la “scuola migliore” dovrebbe essere quella che insegna a sviluppare la giusta mentalità allo scopo. La scelta del linguaggio (o dei linguaggi) da adottare rientra nell’analisi degli strumenti da utilizzare per la soluzione dello specifico problema.
Once you master it
Contrariamente a quanto si potrebbe credere, imparare a programmare in assembly non richiede, in generale, tanto tempo né particolari capacità. Il funzionamento di un processore è abbastanza semplice (parlando un po’ più astrattamente) e richiede l’acquisizione di poche conoscenze di base (sistema binario / esadecimale, registri, accesso alla memoria, istruzioni, come avviene l’esecuzione del codice), per cui ciò si riflette pari pari nella programmazione assembly.
Parlando un po’ più nello specifico, bisogna puntualizzare un paio di aspetti. Il primo è che alcune architetture più vecchie è facile che siano dotate di istruzioni altamente specializzate che potevano essere utili all’epoca (ad esempio quelle per l’elaborazione di informazioni codificate in formato BCD), ma non lo sono più in tempi moderni. Per cui semplicemente si prende atto della loro esistenza, e sostanzialmente le si dimentica, utilizzando tutte le altre per scrivere codice assembly.
Il secondo è che le architetture più vecchie sono pure quelle che in genere mettono meno istruzioni a disposizione, perché all’epoca i transistor erano merce rara e/o costosa e si tendeva a ridurne il più possibile il loro utilizzo. Quindi le architetture non avevano una quantità elevata di istruzioni, per cui erano anche più facili da imparare.
Il problema nasce a partire dalla seconda metà degli anni ’90, dove l’introduzione di istruzioni sempre più specializzate e, in particolare, di quelle relative alle (ormai immancabili) unità SIMD (in salsa “packed“, ai tempi, con l’MMX di Intel a fare da apripista in ambito consumer. Negli ultimi anni, invece, si stanno facendo strada le versioni “vettoriali” o “CRAY-style“) che hanno portato via via all’aggiunta di centinaia e centinaia di tali istruzioni (unica, recentissima, eccezione è l’SVE64 proposta per l’ISA POWER) nonché di registri dedicati, facendo letteralmente esplodere il numero totale di istruzioni di un’architettura.
In sintesi, le architetture più vecchie / “legacy” / “retro” sono in genere le più facili da imparare perché c’è poca roba da assimilare, mentre quelle più nuove (o vecchie che hanno subito un’evoluzione, come le celeberrime x86 o ARM) decisamente no: imparare tutto richiede un’enorme quantità di tempo (anche se si potrebbe diluirne lo studio, procedendo a gruppi di istruzioni simili / dello stesso genere).
Quanto al padroneggiarne (mastering) l’uso, serve ovviamente esperienza nel capire in che modo risolvere i problemi coi pochi strumenti che questo linguaggio mette a disposizione. Quindi roba come l’input, l’output, l’accesso ai file, l’allocazione della memoria, ecc., si imparano sul campo mettendo in fila sequele più o meno lunghe di istruzioni finché non si arriva allo scopo prefisso.
Problemi più complicati richiederanno molto più tempo e istruzioni, come peraltro succede con qualunque altro linguaggio di programmazione, con l’unica differenza rilevante che è data dalla scarsità di strumenti a disposizione, per l’appunto: poca espressività e istruzioni troppo elementari comportano tempi di sviluppo mostruosamente più lunghi (anche di un centinaio di volte, come già detto) nonché listati costituti da centinaia o anche migliaia di istruzioni anche per implementare cose che in altri linguaggi richiedono poche righe.
Padroneggiare l’assembly, quindi, è sostanzialmente una questione di parecchio tempo che bisogna investire per sviluppare la giusta mentalità e la consapevolezza dei pochi mezzi che si hanno per poter realizzare ciò che ci serve. Nulla di concettualmente diverso da altri linguaggi di programmazione, insomma, a parte il tempo da spenderci.
you are a better coder overall because you know how things work
Qui arriviamo alla chicca che tirano fuori gli sfegatati sostenitori di questo linguaggio di programmazione: un’autentica perla di ignoranza condita da una fallacia logica da manuale.
Ignoranza in primis, perché se la necessità fosse quella di sapere “come funzionino le cose” (cioè a basso livello) allora i fanatici dell’assembly dovrebbero, invece, rivolgersi al già menzionato linguaggio macchina, col quale si è obbligati a conoscere per filo e per segno in che modo siano rappresentate effettivamente le istruzioni da eseguire e che vanno in pasto al processore.
L’assembly, infatti, altro non è che un mascheramento del linguaggio macchina. Una versione edulcorata e molto semplificata, insomma, realizzata apposta per i Quiche Eater che non sono abbastanza bravi da padroneggiare il linguaggio che per eccellenza è il più vicino alla macchina e che, ovviamente, è appannaggio dei Real Programmers, com’è ben noto:
Real Programmers wrote in machine code. Not FORTRAN. Not RATFOR. Not, even, assembly language. Machine Code. Raw, unadorned, inscrutable hexadecimal numbers. Directly.
Si potrebbe continuare questa fiera delle assurdità tirando in ballo gli ingegneri microelettronici, poi gli elettronici, i fisici e i matematici, in una continua corsa verso livelli di astrazione sempre più bassi che, “logica” strampalata alla mano (quella dell’autore delle affermazioni), dovrebbe conferire agli highlander rimasti il titolo di più fico in assoluto. Ma mi fermo qui e passo oltre, perché il concetto dovrebbe esser chiaro e la farsa è già durata abbastanza.
Passando alla fallacia logica, la questione è, in verità, di una banalità estrema: in base a quale perverso percorso “logico” (lo metto ancora necessariamente con le virgolette) “conoscere le cose (a basso livello)” renderebbe programmatori migliori? Quali virtù, dunque, sarebbero appannaggio del basso livello che conferirebbero la magica proprietà di divenire automaticamente dei migliori programmatori?
Si tratta di domande che sono destinate a non ottenere risposte, data la totale inconsistenza logica a fondamento dell’assurdità proferita.
It provides more coding freedom. You do whatever you want
L’unica libertà che conferisce l’assembly è quella di spararsi sui piedi (per citare un noto motto anglosassone), e di gran lunga più frequentemente rispetto agli altri linguaggi di programmazione. Un chiaro segnale ai bug di… scatenare l’inferno!
Difatti qui si arriva al paradosso di voler far passare l’assenza di tantissimi controlli, che sono presenti in tanti linguaggi di programmazione, con una presunta libertà di programmazione. Togliere di mezzo “sicurezza” e “robustezza” favorendo la mera scrittura di blocchi di linee di codice di operazioni elementari / primitive quasi senza alcun tipo di controllo di cosa queste facciano e lasciando, quindi, al programmatore l’onere di definirne la semantica, non mi sembra una grande idea. Anzi, personalmente la classificherei come induzione al suicidio.
In assembly è troppo facile sbagliare a passare il dato giusto al posto giusto, perché… sono tutti dati grezzi e totalmente privi di valore per l’assemblatore, che ne sconosce del tutto il significato intrinseco, quale informazione effettivamente trasportano, e dove andrebbero usati. Per lui sono tutti bit e numeri che vengono copiati da un posto a un altro, e la storia finisce lì.
E’ talmente facile che i problemi di corruzione della memoria (giusto per citare i più noti nonché rognosi), avendo avuto accesso ad aree alle quali non si era realmente intenzionati, sono fra i più frequenti. In questi casi andare a scovarne la causa richiede un enorme sforzo che è tutto a carico del programmatore, il quale deve esaminare le istruzioni che si susseguono nel flusso dell’elaborazione, cercando di ricostruirne non solo i passaggi ma anche l’utilizzo che se ne fa, con annessi gran mal di testa.
Tutti controlli che in altri linguaggi vengono, invece, molte volte automaticamente effettuati dal compilatore, producendo errori di sintassi o anche soltanto warning utili a capire che si stia facendo qualcosa di inerentemente sbagliato.
Comunque se i programmatori assembly vogliono essere liberi di spararsi sui piedi, chi siamo noi per impedirglielo?
Obviously your code will be shorter and faster
Freud si farebbe una grassa risata a leggere una simile affermazione, tirando in ballo la mai doma voglia di misurare, misurare, misurare … da parte di certi individui.
Anche assumendo che sia vero, il fatto di avere codice più corto e più veloce cosa comporterebbe nel 2023, con computer in cui la memoria di sistema si misura in diversi GB (e a breve si misurerà in decine di GB) e la frequenza dei processori in diversi Ghz (a cui aggiungiamo ormai le decine di core a disposizione)? Sostanzialmente nulla, nella vita di tutti i giorni.
Ma, soprattutto, la domanda da porsi in questo caso sarebbe: qual sarebbe il costo per ottenere questi benefici?
Perché scrivere codice più corto e veloce non viene automaticamente fuori e richiede tempo. Parecchio tempo, lavorando in assembly. Tempo che, per uno sviluppatore, è estremamente prezioso, poiché o viene pagato dal datore di lavoro oppure (nel caso di progetti amatoriali o personali) viene pagato prendendo una parte della propria, limitata, vita di essere umano.
In asm you can call any function located anywhere in the source, without prior declaration. Last time i’ve looked, in C you had to either pay attention to order, or define same thing several times
Un’altra dimostrazione di ignoranza: in C è possibilissimo, infatti, evitare di definire una funzione prima che la si chiami!
Capita, purtroppo spesso, quando il fanatico programmatore assembly che vive rintanato nella sua grotta comincia a uscire fuori la testa e a trattare argomenti di cui evidentemente mastica poco o nulla.
Molto probabilmente il personaggio in questione non ha aperto nemmeno una volta il K&R in vita sua, ma si è sentito ugualmente in dovere di pontificare dall’alto scranno concessogli dall’essere un programmatore assembly. Sebbene, poi, sia finito con l’inciampare su cose di cui non ha cognizione di causa, probabilmente obnubilato e soggiogato dal delirio di onnipotenza in cui versa grazie al suo stato di “eletto”…
C syntax is simply horrible
Qui si può essere d’accordo o meno: è una mera questione di gusti.
Ma se dovessimo paragonare C e assembly a livello di sintassi, non avrei dubbio alcuno: C tutta la vita!
Non è pensabile, infatti, di poter preferire infinite sequele di istruzioni primitive e rozze che vanno mentalmente decodificate per cercare di capire cosa diavolo stia facendo quel particolare pezzo di codice.
Il C ha certamente i suoi problemi (e sia chiaro che nemmeno a me piace la sua sintassi: amo i linguaggi che siano facili da leggere!), ma lo sforzo di comprenderne un pezzo di codice è enormemente minore rispetto ad analizzarne l’equivalente, ma scritto in un qualunque linguaggio assembly.
Perché bisogna tenere conto anche di un altro, assolutamente non trascurabile, fatto: esistono centinaia di architetture diverse, ognuna con annesso linguaggio assembly dotato di propria sintassi, e alcuni disponendo persino di assemblatori con sintassi diverse per la medesima architettura: un inferno in terra!
In asm you can have nice, aligned, one-comment per code line
In questo caso è George Orwell a ribaltarsi nella tomba, a causa dell’inevitabile citazione al suo 1984 e, nello specifico, del tentativo di ridefinizione della lingua (la famosa “neolingua“).
Si passa, infatti, all’incensare ed esaltare quale meraviglia suprema quella che è, di fatto, l’unica possibilità offerta dal linguaggio assembly: avere commenti su una sola riga. Per giunta allineati (cosa del tutto falsa, peraltro. Infatti possono essere disallineati)! Ullalà!
Meraviglia che, però, è disponibile tranquillamente anche nella maggior parte degli altri linguaggi di programmazione, con molti di loro che offrono pure la possibilità di utilizzare commenti multilinea.
Com’era quella pubblicità? Less is better…
Ma poi commenti… “belli” (nice)? Cosa significa, che i commenti belli si possano scrivere soltanto in assembly?!?
O, forse, la mal celata verità risiede nell’assoluta necessità di scrivere non soltanto commenti, ma che siano pure “belli”, per potersi ricordare di cosa facciano effettivamente quelle righe di codice, vista la cronica assenza di informazioni semantiche nelle istruzioni assembly?
Unlike C, in asm you will not run into undefined behaviours without even knowing it
L’ignoranza torna nuovamente alla carica (o, forse, non se n’è mai andata). Infatti comportamenti indefiniti si trovano anche con l’assembly, e più precisamente riguardano istruzioni o contesti dello specifico processore che la casa madre ha deciso di non esplicitare.
In ogni caso il problema non dovrebbe esistere, a prescindere dal linguaggio: se certi comportamenti è noto essere non definiti, vuol dire semplicemente non dover fare alcuna assunzione valida nel caso si utilizzi quella determinata istruzione / costrutto sintattico. Sic et simpliciter!
E’ un concetto così difficile da comprendere? Non mi pare.
because the spec is so overcomplicated and nobody knows it fully
La chicca, il personaggio, la riserva alla fine: una fallacia logica da manuale, in pieno delirio di onnipotenza.
Premesso che le specifiche del C non sono affatto così complicate (altrimenti ci sarebbe stata una moria di programmatori suicidatisi per aver osato approcciarsi al C++), le specifiche del linguaggio non sono nemmeno così lunghe: occupano circa un centinaio di pagine, peraltro di godibile lettura e con annessi esempi.
Che dire, allora, delle centinaia e centinaia (oggi migliaia e migliaia!) di pagine delle specifiche dell’architettura di un processore? Specifiche che, tra l’altro, sono molto raramente condite da esempi?
Due pesi e due misure? Senz’altro, com’è evidente quando l’allegra macchietta deve continuare nella crociata contro i linguaggi di programmazione a lui sgraditi e nella contemporanea via crucis (perché tale è, se la esaminiamo; come è stato fatto in quest’articolo) incensante il suo meraviglioso assembly.
Tornando alla fallacia logica, il fanatico integralista ci delizia con quella che è, a tutti gli effetti, una candida ammissione della sua incapacità. Perché e siccome non è in grado di comprendere le specifiche del linguaggio C, allora le classifica tout court come ultra complicate, aggiungendo che nessuno le conosce.
Cioè, non le conosce lui, che ha dato ampia prova della sua ignoranza in merito, e quindi “necessariamente” (sic!) non le conosce nessuno.
Che dire: un capolavoro di logica (!) che questa volta riesce a far ribaltare nella tomba contemporaneamente Kurt Gödel e George Orwell (l’ignoranza è forza)!
E’ chiaro, ormai, che le capacità del giullare della programmazione risultino alquanto ridotte e limitate, e che lo rendano, pertanto, inabile a valutare ciò che sia fuori dal suo raggio d’azione, ma ciò non lo autorizza a proiettarle anche agli altri per non sentirsi solo.
Conclusioni
Ci sarebbe ancora molto altro da aggiungere, perché la discussione da cui è scaturito tutto è andata avanti e il Don Chisciotte ha dato ulteriore prova delle sue “abilità” con altre perle da collezione, ma lo scopo del pezzo era di partire da questi spunti per chiarire alcune leggende metropolitane e falsi miti (che spero di aver ben esplicitato), i quali aleggiano ancora oggi sulla famigerata programmazione assembly.
Questo non implica (sempre in tema di logica. Elementare) che non si debba programmare in tale linguaggio o che si debba dileggiare in maniera preconcetta chi lo faccia. Assolutamente no! Alla berlina vanno messi gli pseudo genii che si spacciano come luminari e ricadono nella suddetta tipologia di fanatici integralisti che propugnano le stesse, strampalate, tesi.
Come già detto, programmare in assembly richiede una enorme quantità di tempo per realizzare cose che in altri linguaggi di programmazione si fa tranquillamente anche in un paio d’ordini di grandezza inferiori.
Ognuno è libero di buttare via il proprio tempo come vuole, se ha deciso di farlo con questo linguaggio perché lo diverte e trova appagamento. Cosa che è particolarmente vera nel mondo del retrogaming / retrocomputing: ci sono ancora schiere di appassionati che adorano sviluppare giochi o programmi proprio come ai vecchi tempi, e quindi l’assembly continua a essere molto gettonato. De gustibus!
Discorso diverso a livello a livello professionale, dove il tempo è denaro, il time to market imperativo, e probabilmente si deve anche dar conto agli azionisti dell’azienda (e a seguire tutta la gerarchia che c’è dietro).
Il che, anche qui, non vuol dire che l’assembly debba essere bandito: se serve (e alcune volte serve), lo si usa. Perché la sua scelta è e deve ricadere nell’analisi dei costi / benefici di cui ho parlato prima. Quindi se, nella valutazione, questo strumento ci rientra, allora semplicemente… lo si usa!
L’ingegneria del software (da non confondere con la programmazione!) non si chiama così per puro caso, no?
UPDATE. Un lettore ha fornito un link a un interessante studio in materia: Ada Outperforms Assembly: A Case Study