di  -  mercoledì 8 Settembre 2010

Quando si comincia a parlare di linguaggi di programmazione è facile che la discussione degeneri in confronti e “misure” della relativa “potenza”, intendendo con ciò la possibilità di essere applicati a diversi (numerosi) domini. Ovviamente anche su queste pagine non sono mancate discussioni in merito, in particolare nel recente articolo riguardo alla causa intentata da Oracle nei confronti di Google per Java.

Dal punto di vista strettamente formale il discorso si potrebbe tranquillamente chiudere qui, perché sappiamo bene dalla teoria della calcolabilità che tutti i linguaggi Turing-completi sono equipotenti, quindi sono in grado di elaborare e risolvere esattamente la stessa classe di problemi.

Tutto ciò rimanendo in ambito puramente accademico, ma nella realtà di tutti i giorni non abbiamo a che fare con modelli teorici, macchine dotate di registri o nastri infiniti, ecc., quanto piuttosto di macchine finite con caratteristiche ben precise che limitano ciò che siamo in grado di fare o, matematicamente parlando, di risolvere, e per le quali esiste una moltitudine di linguaggi che devono misurarsi con le risorse a disposizione.

La proliferazione dei linguaggi di programmazione è intimamente legata all’evoluzione dei computer e dei progressi che sono stati fatti a livello tecnologico per permetterci di avere hardware sempre più a buon mercato, e con caratteristiche via via migliori, spalancando le porte del computer domestico (o PC) alla massa, e al contempo a una maggiore astrazione.

I primi computer si programmavano in linguaggio macchina perché non c’era altro mezzo: avevano pochissima memoria e capacità di calcolo, per cui gli sviluppatori dell’epoca erano costretti a conoscere dettagli di bassissimo livello quali la struttura degli opcode del processore, avendo a che fare con sfilze di numeri binari che rappresentavano codice o dati a seconda del contesto.

Con la disponibilità di hardware più capace e l’affinamento delle conoscenze, si è passati a un maggior livello di astrazione, grazie all’introduzione degli assemblatori prima, e di linguaggi di più alto livello come Fortran, Cobol, Lisp, Algol, e così via, fino ad arrivare ai nostri giorni dove assistiamo a un’esplosione di nuovi linguaggi che saltano fuori come funghi, grazie al fatto che realizzare compilatori, interpreti, o “ibridi” (compilatori con runtime che fanno uso di virtual machine) è diventato decisamente più semplice.

L’astrazione ha avuto un duplice, ma importantissimo, scopo nonché implicazione: slegarsi dai dettagli di più basso livello dell’hardware, e fornire agli sviluppatori strumenti più comodi per lavorare, migliorandone la produttività.

Applicare questo concetto (il primo) a un assemblatore sembra, però, un’eresia, in quanto tale strumento è sinonimo di basso livello, appena un pelo sopra il linguaggio macchina, e di cui viene considerato sostanzialmente un “mascheramento”, visto che a uno mnemonico corrisponde un opcode codificato in linguaggio macchina.

In realtà questa visione abbastanza comune non tiene conto delle evoluzioni che ci sono state anche in questo campo. L’introduzione di macro, ad esempio, ha aggiunto notevole flessibilità e capacità di astrazione al linguaggio assembly, come pure l’aggiunta di strutture dati (o record, per gli affezionati del Pascal) con tanto di “ereditarietà” che richiama alla mente la programmazione a oggetti, e altri utili costrutti.

Inoltre non si tiene conto del fattore più importante: l’astrazione offerta dall’assembly permette di slegarsi dall’architettura a cui normalmente si riferisce, e in particolare dal linguaggio macchina che ci sta sotto.

Anche questo può sembrare un attentato alla fede costituita (vista la citata relazione fra mnemonico e opcode), ma un esempio servirà a chiarire meglio il concetto, e riguarda l’8086 di Intel, che al momento dell’introduzione offriva compatibilità a livello sorgente coi precedenti processori della casa, 8008, 8080 e 8085.

Questo era possibile grazie alla presenza di strumenti che convertivano i sorgenti dall’assembly di quei microprocessori a quello 8086. L’architettura di quest’ultimo era diversa, soprattutto a livello di ISA, per cui la compatibilità binaria era fuori discussione, ma grazie a questi programmi di traduzione il parco software dei predecessori poteva essere facilmente portato sul nuovo arrivato.

Può sembrare un caso isolato, ma il concetto è applicabile ad altre architetture, anche moderne. Chiaramente non sempre una traduzione del genere è funzionante, perché dipende da due fattori molto importanti: l’hardware e/o il s.o. su cui gira il processore, che possono inchiodare il codice a quello specifico sistema.

In ultima analisi, bisogna considerare anche le “porcate” che un programmatore assembly può tirare fuori dal cappello; roba perfettamente legittima per quella precisa architettura, ma la cui traduzione risulterebbe poi inutilizzabile. Ad esempio giocare coi limiti dei segmenti a 16 bit degli 8086, o con quelli dello spazio d’indirizzamento a 20 bit e relativo wrapping alla parte bassa della memoria. Oppure, ancora peggio, infilare codice macchina in mezzo a quello assembly, sotto forma di “dati”.

Il passaggio successivo, a linguaggi di più alto livello, ha permesso un’astrazione maggiore e dovrebbe aver rimosso il legame con la particolare architettura, o comunque con dettagli di livello estremamente basso con cui in genere si ha a che fare lavorando in linguaggio macchina o assembly.

Sulla carta, e formalmente, è più o meno così, e a memoria non ricordo linguaggi di alto livello le cui specifiche consentono di accedere a determinati registri di una CPU, a precise locazioni di memoria, o a porte di I/O per le architetture che le mettono a disposizione, giusto per citarne qualcuno.

Infatti spulciando il draft dello standard ISO C99 non si evince nulla del genere. Anzi, il documento è pieno di parti non definite e lasciate liberamente allo sviluppatore. Se consideriamo che il C è il linguaggio che da anni ha ormai preso il posto dell’assembly per l’interfacciamento con l’hardware, c’è di che rimanere a bocca aperta.

Non bisogna, però, meravigliarsi, perché rientra perfettamente nello scopo: astrarre dall’hardware e dai dettagli di basso livello, semplificando la vita ai programmatori e permettendo di scrivere software molto più portabile. Ma come si può pensare di realizzare sistemi operativi per le architetture più disparate senza poter manipolare lo specifico hardware?

Eppure sappiamo che il C come linguaggio è nato proprio con lo scopo di scrivere (velocemente) Unix, s.o. che è stato portato sulle piattaforme più disparate, e non è servito soltanto a questo, ma a realizzare codice che spazia dal basso livello (driver, librerie), ad applicazioni anche molto complesse e addirittura dotate di interfaccia grafica.

Un linguaggio “potente”, quindi, nell’accezione utilizzata all’inizio dell’articolo, ma che non sarebbe tale attenendosi rigorosamente alle specifiche dello standard.

La chiave di volta, infatti, sta nell’andare oltre. Quindi nell’introduzione di costrutti non presenti nel linguaggio (ad esempio funzioni per gestire le porte di I/O) e/o forzando i dettagli che non sono specificati (la dimensione del char, che non è detto coincida col singolo byte), lasciando all’implementazione il compito di definirli e sfruttarli in base agli obiettivi che ci si è posti. O ancora limitando il linguaggio ed eliminando alcune funzionalità intrinsecamente non portabili (ad esempio i campi di bit).

Lo stesso concetto è, però, applicabile anche ad altri linguaggi. Infatti prima del C non è che non esistessero s.o. scritti in linguaggi di più alto livello; e anche dopo l’introduzione di questo linguaggio e l’ampio uso per la programmazione di sistema, ne sono stati scritti in altri linguaggi (ce ne sono anche in Java, e di recente ha fatto notizia Singularity, scritto in C# et similia).

In fine dei conti è l’implementazione che fa la differenza, anche se certamente si può affermare che un determinato linguaggio si presta meglio di altri per determinati scopi. D’altra parte se un linguaggio nasce, è proprio per portare un contributo, un miglioramento a un determinato settore dell’informatica, e come tale dovrebbe essere primariamente utilizzato (il che non significa che non si possa fare altro, sia chiaro).

Non bisogna, però, lasciarsi influenzare dalle implementazioni per fare assunzioni sul linguaggio in generale. Se è vero che un compilatore ottimizzato per il C ne può esaltare le qualità, non possono sprofondare di fronte a una pessima versione. Giusto per essere chiari, non sarebbe certo un interprete C a togliere meriti a questo linguaggio e alla sua “potenza”, come per qualunque altro.

Questo perché un linguaggio rimane rappresentato dalla sintassi e semantica che i suoi creatori gli hanno conferito. Con buona pace di qualunque implementazione…

35 Commenti »

I commenti inseriti dai lettori di AppuntiDigitali non sono oggetto di moderazione preventiva, ma solo di eventuale filtro antispam. Qualora si ravvisi un contenuto non consono (offensivo o diffamatorio) si prega di contattare l'amministrazione di Appunti Digitali all'indirizzo info@appuntidigitali.it, specificando quale sia il commento in oggetto.

  • # 1
    wdasm
     scrive: 

    io scrivo in c# da quasi 10 anni ma alle volte devo scrivere ancora librerie in c per effettuare delle cose fatte bene. Comunque pascal, c e chissà quali altri che non ricordo permettevano la scrittura di codice assembly.. e a quell’epoca tutto ciò che non era asm era alto livello. ora se il c è diventato basso livello quello non lo sapevo..

  • # 2
    Xeus32
     scrive: 

    Effettivamente vero.
    Io lavoro regolarmente in c/c++/c# e spesso lavoro su microcontrollori. Non mi è nuovo scrivere pagine di asm sopratutto per ARV dove ti devi aggrappare a 1k di ram.
    Dopo anni di sviluppo sto lavorando bene con il c# ma da troppi problemi di portabilità (al di fuori di windows) e spesso non si possono accedere a funzioni hw come mmx/sse.
    Come dice wdasm, il c# è molto comodo e veloce ma devi sapere anche almeno il c altrimenti hai dei vincoli che non puoi superare.
    Purtroppo però il mercato premia il prodotto sviluppato in fretta e il cliente non valuta il peso di utilizzare le vm per far eseguire il proprio codice.
    Ho dei clienti che utilizzano ancora pentium 2 per acquisizione dati con software scritti in delphi e vanno 10 volte più veloci di uno scritto in c# su una macchina recente (sarà anche windows 7 vs windows NT 4.0)!

    Scusate ma io convinto che in windows 7 i servizi fossero stati riscritti in c# per maggiore stabilità?!?

  • # 3
    pabloski
     scrive: 

    @wdasm: le impleementazioni attuali di pascal e c permettono ancora di inserire codice assembly….addirittura delphi ha conservato questa caratteristica del pascal

    comunque sui linguaggi bisogna essere più pragmatici, nel senso che un linguaggio nasce con lo scopo di rendere la vita facile al programmatore non alla macchina e questo concetto dovrebbe essere tenuto a mente quando di valuta un linguaggio

    dire che il linguaggio X fa pena perchè è N volte più lento del C è una sciocchezza colossale…se prendiamo il web, ad esempio, quanto può influire la scelta di usare il php invece del c? quali vantaggi potrebbe portare il c ad un’applicazione il cui lavoro è fondamentalmente quello di comunicare con un dbms ( scritto in c e a ragione in questo caso )

    il dover forzare le specifiche del linguaggio per rendere possibili cose altrimenti impossibili, imho, dovrebbe essere evitato il più possibile…nel caso del C è stato necessario, altrimenti nada sistemi operativi in C, ma guai a pensare di implementare istruzioni per l’I/O in tutti i linguaggi solo per farli sembrare più “potenti”

    per fortuna i moderni linguaggi orientati alla programmazione non di sistema nemmeno si sognano di fare cose simili e mantengono così la loro purezza

    complessivamente si può individuare un pattern nell’evoluzione dei linguaggi e ritengo che sia anche un percorso ben fatto e razionale….

    – assembly ( evita di perdere la vista e la testa con gli opcode )

    – C ( rende portabile il software, una necessità nata dal boom dei microcomputer )

    – pascal, fortran, ecc… ( si comincia a delineare una strategia che punta a rendere la vita semplice al programmatore )

    – java, c#/.net, ecc… ( si punta all’indipendenza dall’hardware e dal sistema operativo e si delinia una strategia per creare software più sicuro )

    – php, python e soci ( rendere il software rapidissimo da sviluppare, con poco codice e conseguenti meno guai per il mantenimento/debug e nel contempo offrire una totale indipendenza dal sistema sottostante )

    – scala, f#, ecc… ( penso che in futuro guadagneranno consensi, il loro scopo è rendere il software ancora più sicuro…scala ha inoltre la pretesa di rendere facile il multithreading )

    giustamente qualcuno dirà “ma le performance?”….e infatti questo è stato l’elemento che fino ad oggi è stato tenuto fuori dall’equazione MA….ma qualunque c’ha pensato e infatti è nato il progetto llvm che mira a rendere i linguaggi di alto livello molto più performanti di quello che sono oggi

    ok, jvm, .net, pure loro ottimizzano ma di certo non nei termini, con le possibilità e le potenzialità di llvm….del resto llvm essendo una vm di basso livello riesce a catturare dettagli che le altre vm non possono catturare e usare queste informazioni ( e quelle sul processore target ovviamente ) per ottimizzare in maniera aggressiva il codice

  • # 4
    megawati
     scrive: 

    Mah… dice l’articolo che anche gli assembler ormai sono astratti e potenti. Xeus32 dice che deve scrivere in asm puro quando serve velocità e compattezza. Insomma mi pare che ci sia qualcosa che non torna.

    IMHO, molto dipende dal tipo di astrazione che il linguaggio fa: se il modello di astrazione è sbagliato il linguaggio poi ne risente e hai voglia a ottimizzare i compilatori.
    Vedi l’esempio Delphi-C# fatto da Xeus32: secondo me il modello di astrazione di Delphi (qualunque sia) è migliore di quello adottato da C#. Il fatto che C# sia interpretato non è rilevante perché il codice viene compilato JIT, quindi da quel punto di vista siamo pari.

    Secondo me la domanda da farsi è: qual’è, al momento, il miglior modello astratto implementato in un linguaggio? O altrimenti: quale linguaggio implementa la migliore astrazione di oggetti hardware? Rispondendo a questa domanda si potrebbe allora stabilire quale linguaggio è più potente… IMHO

  • # 5
    TheFoggy
     scrive: 

    Mi son perso un pezzo..ma c# non dovrebbe “per sua natura” accedere alle funzioni SSE, nel caso siano disponibili? Se no, a cosa serve la tanto decantata compilazione JIT? Avevo letto dei test, tempo fa, in cui si facevano i classici confronti tra C e C# riguardanti ordinamenti di vettori, e cose simili e, con mia sorpresa, il codice C# spesso era più veloce dello stesso codice scritto in C! (credo, però, che il codice C fosse “ottimizzato” per le SSE, mentre il C# potesse usare anche le SSE4). Detto questo, io sono un sostenitore del C, in quanto permette di avere tutto (o quasi..) sotto controllo, con i suoi pro e i suoi contro. Cresciuto col Pascal (che ritenevo troppo “ad alto livello”, ma alle superiori insegnavano questo, essendo di facile apprendimento), sono passato al C e non me ne sono più andato. (salvo fare un saltino nel campo dell’assembly quando si parla di mocrocontrollori risicati – se no opto per il C, per questioni di tempistiche – o driver un po’ strani..)
    Certo, spesso il C rende la stesura dei programmi più lenta e macchinosa (e quindi costosa..), ma di norma le prestazioni ne giovano. Ovviamente, come dice pabloski, dipende sempre dal campo di utilizzo. Il php va benissimo, anche se perde, che so, 1ms ad ogni operazione, rispetto al C. Il programmatore ne guadagna, anche e soprattutto in fase di manutenzione, e all’utente cambia poco. Ma per un gioco, il C resta preferibile, IMHO. Anche se c’è questa cosa della compilazione JIT del C# che non mi è ben chiara e mi spingerebbe a provare a fare un’applicazione grafica in DirectX, tanto per fare qualche raffronto.. Se qualcuno ne ha già fatti, invece, curioso di conoscerne i risultati! :)

  • # 6
    homero
     scrive: 

    concordo con quanto scritto da cdmauro nell’articolo…
    chiedo venia perchè effettivamente nel draft del C99 non è presente in modo chiaro il fatto che i puntatori devono far riferimento a delle precise locazioni di memoria come avevo scritto in qualche post precedente…anche se in questo caso la disquisizione sarebbe lunga…
    concordo con la teoria di turing che ho lungamente studiato in calcolo numerico…
    vorrei focalizzarmi sulle architetture di calcolo oggi a disposizione degli utenti generici…
    ricordo a tutti che chi esegue il calcolo è l’hardware e non il software e pertanto il software e l’hardware devono sempre andare di pari passo…un tempo si scriveva che il software non riesce a tenere il passo dell’hardware…ai tempi del dos e dei 486…oggi dico che l’industria del software vuole astrarsi dall’hardware…
    ma se questo dovesse malauguratamente accadere gli unici a perdere saranno gli utenti…a prescindere dai linguaggi di programmazione…

  • # 7
    Cesare Di Mauro (Autore del post)
     scrive: 

    @wdasm: se leggi bene, non ho mai detto che il C è un linguaggio di basso livello. ;)

    Per quanto riguarda il Pascal e C, all’epoca avevano delle estensioni allo standard per inserire codice assembly (anzi, prima c’era soltanto la possibilità di inserire codice macchine, tramite roba come le inline del Turbo Pascal), manipolare le porte di I/O, scrivere funzioni per gestire interrupt. Ma non facevano parte del draft dei rispettivi linguaggi.

    @Xeus32: C# permette di accedere a roba come le SSE (sulle MMX non ci giurerei, essendo troppo vecchie come estensioni), ma non direttamente. E’ compito del compilatore JIT, a runtime, generare codice che faccia uso delle estensioni che trova disponibili.

    Non mi risulta che i servizi di 7 siano scritti in C#, ma potrebbero anche esserlo.

    @megawati: dai un’occhiata a questo http://en.wikipedia.org/wiki/High-level_assembler e in particolare a quest’altro http://en.wikipedia.org/wiki/High_Level_Assembly link. ;)

    Per quanto riguarda Delphi vs C#, faccio presente che già da anni è disponibile Delphi.NET, le cui applicazioni girano su .NET come C#, appunto. ;)

    Questo per rimarcare sempre il concetto di linguaggio inteso come sintassi e semantica, dalla sua implementazione (anche se nel caso di Delphi.NET non tutti i programmi Delphi/Win32 possono essere portati facilmente).

    Rispondere alle tue domande è difficile. Mi servirebbe capire prima cosa intendi di preciso con “migliore astrazione di oggetti hardware”.

    @TheFoggy: ricordi bene. Aggiungo che un programma compilato in x86 usando le SSE2, ad esempio, non potrà mai beneficiare delle SSE3, SSE4, delle prossime AVX, ecc. Mentre qualunque programma .NET sì…

    Quanto ai giochi, sono passati da tempo al C++, perché il basso livello di astrazione offerto dal C non è sufficiente per gestire progetti di questa portata.

    @homero: l’industria del software già da tempo si sta astraendo dall’hardware, e non potrebbe essere altrimenti vista la complessità raggiunta dalle applicazioni, anche semplici.

    Prova a pensare a un “Hello, world!” realizzato però con un’interfaccia grafica, e alle varie implementazioni in assembly, C, C++, Delphi, ecc.

    Se abbiamo tonnellate di software più o meno complessi è proprio grazie all’astrazione. :P

  • # 8
    Marco
     scrive: 

    @pabloski
    \- C ( rende portabile il software, una necessità nata dal boom dei microcomputer )\

    Solo qualche appunto al \pattern nell’evoluzione dei linguaggi\ che hai individuato.

    Il Pascal, la cui nascita è antecedente (o quantomeno contemporanea) al C, è ugualmente portabile e adatto alla scrittura di un SO. Ha introdotto la programmazione strutturata.
    Il FORTRAN è stato uno dei primissimi linguaggi \ad alto livello\ di successo, analogamente portabile. La necessità della portabilità era sentita già negli anni ’50.
    Il boom dei microcomputer (e finanche la nascita del microprocessore) è successivo alla nascita del C. Il panorama dei computer e dei SO disponibili all’epoca era tuttavia ancora più frammentato di quello dei microcomputer negli anni ’80, per cui come già detto la portabilità era già da tempo un plus non indifferente.

  • # 9
    Nessuno
     scrive: 

    [quoteblock]Quanto ai giochi, sono passati da tempo al C++, perché il basso livello di astrazione offerto dal C non è sufficiente per gestire progetti di questa portata.[/quoteblock]
    Vorrei far notare che le capacità di gestire “il basso livello” del C e del C++ sono perfettamente le medesime, per cui usare il C++ vs C quando le risorse lo permettono non altera in alcun modo l’astrazione dell’HW, come invece accade usando altri tipi di linguaggi e relativi framework/VM.
    Il C++ ha nativamente costrutti e paradigmi molto più potenti del C e quindi è logico usarlo dove la complessità del codice è medio alta.

    L’articolo lo trovo interessante, ma piuttosto “limitato” nell’analisi. SI fa riferimento alla “potenza” del linguaggio senza fare riferimento alla sua implementazione. Credo che si sia fatta confusione tra il concetto di “astrazione del linguaggio” con quello che il linguaggio permette di fare, che sono due cose differenti. Qualche esempio:
    L’ASM puro (senza macro assembler) è una astrazione 1:1 del linguaggio macchina inteso come scrittura binaria del codice ma non astrae proprio nulla dell’HW sottostante. C# è un linguaggio che astrae sia diverse modalità di programmazione tipiche dei linguaggi come il C (formalismi per il controllo del flusso del programma, strutture etc…), ma anche una serie di altre cose, come ad esempio il concetto di puntatore. Questo tipo di astrazione è diversa da quello che poi è l’astrazione dell’HW su cui il SW gira e rende diversi i due linguaggi già per queste caratteristiche oltre al fatto che C# ha sotto di sè una VM che astrae completamente tutta un’altra serie di cose.
    Usare il linguaggio C piuttosto che il PHP otterrebbe esattamente gli stessi identici risultati (per chi conosce il C non ci sarebbe nessun problema a fare esattamente quello che fa un programma PHP) dato che il linguaggio è comunque interpretato e quindi la differenza lo fa l’interprete (certamente non scritto in PHP).

    Quindi, secondo me, parlare di “potenza” del linguaggio senza rapportarlo alla sua implementazione non ha senso. Java non è potente quanto il C++ per via della VM che gli sta sotto. C# è nello stesso brodo. Ma lo è anche C++ per .NET. Stessa cosa per i linguaggi che si appoggiano ad un interprete qualsiasi, dove la ” potenza” non è quella espressiva o formale propria del linguaggio, ma quella che la sandbox nel quale il codice gira permette.
    Evidenziando quindi che ci sono 2 tipi di astrazione in gioco.

  • # 10
    banryu
     scrive: 

    @Cesare Di Mauro:
    Fantastico articolo, grazie di averlo scritto!
    In termini di \astrazione\ di un linguaggio, mi pare che un fattore che \la fa da padrone\ sia ad esempio la gestione automatica della memoria.

    P.S.:
    ————————
    Quoting Cesare DI Mauro:
    ————————

    Sulla carta, e formalmente, è più o meno così, e a memoria non ricordo linguaggi di alto livello le cui specifiche consentono di accedere a determinati registri di una CPU, a precise locazioni di memoria, o a porte di I/O per le architetture che le mettono a disposizione, giusto per citarne qualcuno.

    Infatti spulciando il draft dello standard ISO C99 non si evince nulla del genere.

    ———————–
    Non so cosa dica lo standard ISO C99, ma supponiamo di dover chiamare una funzione il cui indirizzo sia memorizzato alla locazione 0, si può fare così:
    (*(void(*)())0)();

  • # 11
    Cesare Di Mauro (Autore del post)
     scrive: 

    @Nessuno:

    Vorrei far notare che le capacità di gestire “il basso livello” del C e del C++ sono perfettamente le medesime, per cui usare il C++ vs C quando le risorse lo permettono non altera in alcun modo l’astrazione dell’HW, come invece accade usando altri tipi di linguaggi e relativi framework/VM.
    Il C++ ha nativamente costrutti e paradigmi molto più potenti del C e quindi è logico usarlo dove la complessità del codice è medio alta.

    Io non ho parlato di “basso livello”, per il quale sia C che C++ offrono gli stessi strumenti.

    Ho parlato, invece, di “basso livello di astrazione”, e qui entra in gioco quello che hai scritto poi tu alla fine (e a cui ovviamente mi riferivo).

    L’articolo lo trovo interessante, ma piuttosto “limitato” nell’analisi. SI fa riferimento alla “potenza” del linguaggio senza fare riferimento alla sua implementazione. Credo che si sia fatta confusione tra il concetto di “astrazione del linguaggio” con quello che il linguaggio permette di fare, che sono due cose differenti.

    L’articolo l’ho scritto proprio per evidenziare la differenza che c’è fra “linguaggio” (di programmazione), che esprime soltanto una sintassi e una semantica, e le sue “implementazioni”.

    Il concetto di “potenza” a mio avviso si applica soltanto alle implementazioni, perché i linguaggi offrono generalmente un notevole livello di astrazione dai dettagli di basso livello, da impedirne l’uso per questi scopi senza fare delle assunzioni che, appunto, vanno oltre la definizione del linguaggio stesso.

    Qualche esempio:
    L’ASM puro (senza macro assembler) è una astrazione 1:1 del linguaggio macchina inteso come scrittura binaria del codice ma non astrae proprio nulla dell’HW sottostante.

    Questo non è vero per due motivi.

    Prima di tutto il concetto di “asm puro” non è definito o non esiste; per un determinato processore la casa produttrice produce un annesso assembler e/o definisce il linguaggio assembly da utilizzare, in cui generalmente sono presenti caratteristiche di più alto livello rispetto alla mera conversione da mnemonico al binario dell’opcode corrispondente.

    Secondo, commetti un errore affermando che l’assembly è una “mappatura 1:1” del linguaggio macchina. Non è così perché, ad esempio, il linguaggio macchina può supportare un numero più elevato di opcode, mentre l’assembly mapperà un certo mnemonico con UN solo e preciso opcode.

    L’esempio classico in questo caso è l’architettura x86, che a fronte di un’istruzione INC EAX che viene mappata in un certo opcode, a livello di ISA ne esistono altri (e sto parlando al plurale non a caso) opcode che fanno esattamente la stessa cosa.

    C# è un linguaggio che astrae sia diverse modalità di programmazione tipiche dei linguaggi come il C (formalismi per il controllo del flusso del programma, strutture etc…), ma anche una serie di altre cose, come ad esempio il concetto di puntatore. Questo tipo di astrazione è diversa da quello che poi è l’astrazione dell’HW su cui il SW gira e rende diversi i due linguaggi già per queste caratteristiche oltre al fatto che C# ha sotto di sè una VM che astrae completamente tutta un’altra serie di cose.

    C# può essere compilato in binario puro, come avviene per Java col progetto gcj.

    E questo proprio perché parliamo di linguaggio, e non di UNA sua attuale implementazione.

    Usare il linguaggio C piuttosto che il PHP otterrebbe esattamente gli stessi identici risultati (per chi conosce il C non ci sarebbe nessun problema a fare esattamente quello che fa un programma PHP) dato che il linguaggio è comunque interpretato e quindi la differenza lo fa l’interprete (certamente non scritto in PHP).

    Non vedo perché l’interprete PHP non potrebbe essere scritto in PHP stesso.

    Non so se è stato fatto, ma per Python, ad esempio, c’è il progetto PyPy. Giusto per fare un esempio di linguaggio utilizzato per realizzare interpreti o compilatori del linguaggio stesso.

    Anzi, questa è una cosa che normalmente si fa, anche soltanto come esercizio di stile.

    Quindi, secondo me, parlare di “potenza” del linguaggio senza rapportarlo alla sua implementazione non ha senso. Java non è potente quanto il C++ per via della VM che gli sta sotto. C# è nello stesso brodo. Ma lo è anche C++ per .NET. Stessa cosa per i linguaggi che si appoggiano ad un interprete qualsiasi, dove la ” potenza” non è quella espressiva o formale propria del linguaggio, ma quella che la sandbox nel quale il codice gira permette.

    E allora parliamo dell’implementazione e non del linguaggio, come già detto prima.

    Evidenziando quindi che ci sono 2 tipi di astrazione in gioco.

    Per me linguaggio e implementazione sono due cose diverse. Intimamente connesse, senza dubbio, ma fare affermazioni sul primo citando la seconda non ha senso, se non limitandosi strettamente alla sintassi e all sematica definita dal primo (che, però, nulla dicono sulle implementazioni, a cui lasciano margini, com’è giusto che sia).

    @banryu:

    Fantastico articolo, grazie di averlo scritto!
    In termini di \astrazione\ di un linguaggio, mi pare che un fattore che \la fa da padrone\ sia ad esempio la gestione automatica della memoria.

    Grazie. :)

    Sì, ma non è sempre detto; penso che sia sempre necessario vedere il draft del linguaggio, per vedere se preveda esplicitamente l’uso di un garbage collector (o di meccanismi “trasparenti” di new & delete).

    Ad esempio, al momento in CPython qualunque cosa è un puntatore a una particolare struttura che deriva da PyObject. E quindi c’è memoria che dev’essere allocata anche per un semplice intero.

    Ma questo perché l’attuale implementazione mainstream è fatta in questo modo. Nulla toglie che si possa realizzare un sistema ibrido, per cui alcune volte utilizza un PyIntObject, e altre volte un int (proprio come tipo del C); e sta tranquillo che si può fare. ;)

    Non so cosa dica lo standard ISO C99, ma supponiamo di dover chiamare una funzione il cui indirizzo sia memorizzato alla locazione 0, si può fare così:
    (*(void(*)())0)();

    Lo standard in questo specifico caso, e solo in questo, prevede che la costante 0 venga convertita a un puntatore nullo.

    Ma già convertire da puntatore nullo a intero è un dettaglio dell’implementazione. Trovi tutto a pagina 59 del draft. ;)

  • # 12
    homero
     scrive: 

    che senso ha scindere un linguaggio dalla sua implementazione?
    allora un linguaggio qualunque linguaggio ha una utilità nel momento in cui puo’ produrre qualcosa…
    la lingua italiana è tale in quanto nei secoli attraverso questa lingua si sono scritti trattati, poesi, romanzi, opere liriche, di tale importanza da essere necessario apprendere questo linguaggio che si è meritato sul campo il suo merito, e questo vale per ogni linguaggio, l’esperanto una lingua costruita dagli accademici non ha avuto seguito semplicemente perchè in esperento non si è riusciti a produrre nulla di interessante….
    per quale ragione studiamo ancora il latino? semplicemente perchè in questa lingua milioni di persone hanno prodotto testi, tecnici, artistici, storici, legali….che hanno un valore per l’umanità intera…
    perchè oggi usiamo l’inglese? perchè negli ultimi 2 secoli in questa lingua si sono espresse la maggior parte delle teorie scientifiche contemporanee, che rendono necessaria la conoscenza di questa (che io non reputo neppure una lingua, ma un dialetto)…
    l’inglese scritto è di grandissima facilità a differenza dell’inglese parlato…anche se quando scrivo in inglese mi sembra di esprimermi come un bambino di 5 anni avendo una costruzione del periodo a dir poco ridicola…
    ma ha avuto successo grazie agli enormi finanziamenti alla ricerca che nel ‘900 sono stati profusi dagli stati uniti…rendendo questa lingua un standard de facto in campo scientifico….in futuro forse la lingua cinese soppianterà l’inglese chi puo’ dirlo…resta il fatto che il linguaggio è vincolato a quello che viene prodotto in questo linguaggio…
    a prescindere dalla sua struttura…..
    tornando ai linguaggi di programmazione…
    ….il vantaggio del C è stato senza dubbio quello di produrre software di altissima qualità, facile da mantenere che ha avuto prestazioni costanti nel tempo…la vittoria di tutto questo è stato il fatto di poter dare la possibilità di interfacciarsi con le parti piu’ vicine all’hardware del sistema se non proprio direttamente con l’hardware….
    il fatto di linkare direttamente alla memoria grazie ai puntatori nelle implementazioni precedenti fino ad oggi è stato un strumento importante…benchè oggi i compilatori .net non si sa piu’ dove linkano i puntatori…in quale stack sperduto….resta il fatto che tutto andando male una bella istruzione asm ti salva dagli impicci piu’ perniciosi…
    i compilatori sono migliorati anno dopo anno rendendo questo linguaggio estremamente potente…
    …la struttura modulare basata sulle librerie prima e sulle classi poi lo ha reso insostituibile in alcuni ambiti…il kernel linux non esisterebbe senza il C cosi’ come molti dei software dell’amiga…
    pertanto il C si è conquistato sul campo la bontà di miglior linguaggio e come tale va preservato…
    non vedo al momento all’orizzonte niente che possa sostituirlo…
    i vari python, java, .net sono trovate commerciali che da un lato generano programmatori mediocri, dall’altro vincola in maniera radicale il programmatore all’implementazione….in questo il C è stato sempre di gran lunga piu’ aperto…infatti i primi software in C contenevano sempre qualche routine in assembler….
    ora quando mi scrivete che in php si puo’ scrivere un interprete php….mi fate semplicemente ridere…cosi’ come mi fanno ridere quelli che scrivono un interprete python in python….perchè alla fine dei conti è come dire che con una macchina di touring si puo’ scrivere un interprete php…in teoria tutto è possibile, ma la pratica dimostra altro…
    un appunto lo faccio a microsoft
    MICROSOFT E’ UNA AZIENDA CHE HA SEMPRE LASCIATO A PIEDI I PROPRIO UTENTI. L’unico software che ha dovuto rilasciare per rendere possibile lo sviluppo di applicazioni su windows è stato il visual C, tutte le altre applicazioni che millantavano di essere rapid programming sono state un disastro totale…
    dal visual basic, al visual basic script, ai vari ADO, ai data base access e chi piu’ ne ha piu’ ne metta…
    visual JAVA compreso…
    ed oggi .NET…
    faccio un esempio di stamattina il buon iexplorer9 il browser piu’ usato al mondo ha un supporto dei CSS penoso…
    ho dovuto inserire text-align in un css dove negli altri browser
    e dico in tutti gli altri browser non era necessario…
    questa è microsoft…se ne infischia degli standard se ne infischia dei propri utenti…clona qualunque cosa…
    adesso ha clonato pure la tecnologia flash di adobe…
    .NET ha 10 anni di sviluppo ed oggi non ricordo una sola applicazione scritta per questo framework degna di nota…
    cosi’ come non ricorda una sola applicazione sviluppata in visual basic sopravvissuta….e all’epoca molti ridevano perchè io programmavo ancora in C millantando tempi di sviluppo lunghi…
    adesso i miei programmi in C continuano a rullare portati anche su altri OS…i loro in visual basic non girano piu’ neppure in windows seven…questa è microsoft…solo profitto…
    state alla larga da java e .net se non volete buttare tempo e danaro…
    il C ad oggi non mi mai tradito…

  • # 13
    Nessuno
     scrive: 

    [quote]E allora parliamo dell’implementazione e non del linguaggio, come già detto prima.[/quote]
    E allora parliamo di quello che stavo giusto dicendo, che la “potenza” del linguaggio non è misurabile se non tenendo conto della sua implementazione.
    Nel tuo articolo questa cosa non è chiara, almeno per me, dato che parli di “potenza” di un linguaggio prima di fare riferimento al concetto di implementazione che nomini solo alla fine.

    Il linguaggio ASM è una rappresentazione 1:1 del linguaggio macchina. Ovviamente posso fare un macro assembler che estrae parte dell’HW (o delle funzionalità) e un ASM che non è completo rispetto alle istruzioni che esistono. Stai ancora confondendo il linguaggio con la sua implementazione.
    Se vuoi ti faccio gli esempi di assemblatori che mappano 1:1 le istruzioni del micro senza aggiunte e quelli che usano macro istruzioni per semplificare la ripetizione di micro sequenza ripetitive.
    Se la Intel ha deciso che la sua ISA deve essere ridondante ma il linguaggio ASM no, è una scelta di implementazione del linguaggio ma non cambia assolutamente nulla quali siano le istruzioni disponibili al programatore (averne 10 che fanno la stessa identica cosa e usarne una di queste arbitrariarmente non cambia nulla a livello di programmazione).

    La doppia astrazione a cui mi riferivo, e che non sembra che sia stata compresa la si vede in questo esempio: ammettiamo che tu riesca a fare un programma in C# che bypassa completamente le limitazioni imposte dal framework in cui gira. Proviamo a fare un driver per Linux, o anche direttamente per un qualsiasi micro senza OS, e vediamo se riesci a fare le stesse cose che io faccio in C. Magari preferisci Java al posto di C#. O Python, o qualsiasi altro linguaggio che è completamente pensato per essere astratto dall’HW su cui gira. E’ palese che le funzionalità esposte dal C/C++ sono completamente diverse (e più complete) da quelle che sono esposte da questi altri linguaggi.

    Sebbene in C tu dica che non vi sia una regola che definisca come il linguaggio debba accedere ad una locazione precisa di memoria non toglie il fatto che il linguaggio lo permetta. In Java non si può. Qualsiasi costrutto tu tenti di fare con tale sintassi, non ci riuscirai mai. Semplicemente perchè il linguaggio non è pensato per fare quello che fa il C/C++. Anzi, all’opposto è pensato per evitare che alcune cose possibili con questi linguaggi siano implementabili (aritmetica dei puntatori per esempio). Cosa che ad alto livello è caldamente sconsigliabile usare, ma a basso livello è linfa vitale e un linguaggio che ne impedisce l’uso semplicemente non è adatto a fare il basso livello.

  • # 14
    Cesare Di Mauro (Autore del post)
     scrive: 

    @homero:

    che senso ha scindere un linguaggio dalla sua implementazione?

    Perché sono due cose diverse, e di implementazioni ne possono esistere a josa.

    allora un linguaggio qualunque linguaggio ha una utilità nel momento in cui puo’ produrre qualcosa…

    Non v’è dubbio.

    ….il vantaggio del C è stato senza dubbio quello di produrre software di altissima qualità, facile da mantenere

    Non concordo assolutamente con entrambe le cose. Anche perché la qualità del codice la fa generalmente il programmatore, a prescindere dal linguaggio di programmazione.

    che ha avuto prestazioni costanti nel tempo…

    Mah I compilatori sono migliorati, gli interpreti pure, gli “ibridi” (compilatori che producono bytecode con virtual machine annessa) idem. Qualunque sia il linguaggio.

    la vittoria di tutto questo è stato il fatto di poter dare la possibilità di interfacciarsi con le parti piu’ vicine all’hardware del sistema se non proprio direttamente con l’hardware….
    il fatto di linkare direttamente alla memoria grazie ai puntatori nelle implementazioni precedenti fino ad oggi è stato un strumento importante…

    Per questo devi ringraziare le implementazioni che l’hanno permesso. Quando è stato possibile (vedi modalità protetta di 386+, dove generalmente puntatore = offset e non indirizzo completo costituito da segmento più offset).

    benchè oggi i compilatori .net non si sa piu’ dove linkano i puntatori…in quale stack sperduto….

    Immagino che sia l’heap, se ho capito bene. Ma questo lo fa anche il C con le malloc e le free.

    resta il fatto che tutto andando male una bella istruzione asm ti salva dagli impicci piu’ perniciosi…

    E qui andiamo un po’ fuori dal linguaggio, no?

    i compilatori sono migliorati anno dopo anno rendendo questo linguaggio estremamente potente…

    Questo vale per tutti i linguaggi, come dicevo prima.

    …la struttura modulare basata sulle librerie prima e sulle classi poi lo ha reso insostituibile in alcuni ambiti…

    Oddio, modulare il C. E il Turbo Pascal, allora?

    il kernel linux non esisterebbe senza il C

    Senza dubbio. Anche perché stiamo parlando di un s.o. Unix-like.

    cosi’ come molti dei software dell’amiga…

    Qui ci starei più attento, visto che moltissimi erano scritti… in assembly. Io il C l’ho usato soltanto per qualche progetto universitario, ai tempi.

    pertanto il C si è conquistato sul campo la bontà di miglior linguaggio e come tale va preservato…

    Stai facendo il passo più lungo della gamba. Il miglior linguaggio non esiste: esiste soltanto quello migliore per risolvere lo specifico problema.

    non vedo al momento all’orizzonte niente che possa sostituirlo…

    Beh, ci sono C++ e D come minimo. Poi personalmente preferirei Delphi o Free Pascal.

    i vari python, java, .net sono trovate commerciali

    Premesso che non sono assolutamente d’accordo, quando Python è nato non c’era NESSUN colosso dietro. Sei totalmente fuori strada.

    che da un lato generano programmatori mediocri

    Potrei dire la stessa cosa riguardo al C. Perché la mediocrità non dipende dal linguaggio, ma dalla persona.

    dall’altro vincola in maniera radicale il programmatore all’implementazione…

    Anche qui, in totale disaccordo. Esistono diverse implementazioni di Java, C#, Python, come di altri linguaggi.

    in questo il C è stato sempre di gran lunga piu’ aperto…infatti i primi software in C contenevano sempre qualche routine in assembler….

    Cioè? Non capisco.

    ora quando mi scrivete che in php si puo’ scrivere un interprete php….mi fate semplicemente ridere…cosi’ come mi fanno ridere quelli che scrivono un interprete python in python….perchè alla fine dei conti è come dire che con una macchina di touring si puo’ scrivere un interprete php…in teoria tutto è possibile, ma la pratica dimostra altro…

    Guarda che non c’è nulla di teorico in quello che ho scritto.

    Tra l’altro PyPy, l’implementazione di Python scritta in (R)Python, è il candidato numero uno per sostituire CPython (l’attuale versione mainstream) che, come avrai capito già dal nome, è scritto in C.

    All’ultima PyCon a Firenze PyPy ha letteralmente polverizzato CPython. Quando saranno pronti i video, ti passerò il link.

    Dovresti evitare di parlare di cose che non conosci.

    faccio un esempio di stamattina il buon iexplorer9 il browser piu’ usato al mondo ha un supporto dei CSS penoso…
    ho dovuto inserire text-align in un css dove negli altri browser
    e dico in tutti gli altri browser non era necessario…

    IE6 (il 9 non è ancora uscito) è del 2001, all’epoca i CSS2 non erano ancora standardizzati, e Microsoft non ha disposizione macchine del tempo.

    questa è microsoft…se ne infischia degli standard

    Questo è opinabile. Vedi il caso OpenDocument, di cui ho già parlato in un articolo. E non il solo caso, vedi questo http://www.programmazione.it/index.php?entity=eitem&idItem=45320 articolo.

    se ne infischia dei propri utenti…

    Non sarebbe arrivata dov’è se fosse come dici tu.

    clona qualunque cosa…

    Dall’invenzione della ruota in poi è stato tutto un copia copia. Non c’è nessuno che si salva.

    adesso ha clonato pure la tecnologia flash di adobe…

    Boom. La stai sparando grossa. Se ti riferisci a SilverLight sei completamente fuori strada, e t’invito a studiartelo prima di affermare una cosa che non sta proprio in piedi.

    .NET ha 10 anni di sviluppo ed oggi non ricordo una sola applicazione scritta per questo framework degna di nota…

    Paint.NET?

    cosi’ come non ricorda una sola applicazione sviluppata in visual basic sopravvissuta….

    E invece continuano a circolare. Mi sembra che Project Dogwaffle, una sorta di clone avanzato di Deluxe Paint, sia scritto in questo linguaggio.

    e all’epoca molti ridevano perchè io programmavo ancora in C millantando tempi di sviluppo lunghi…
    adesso i miei programmi in C continuano a rullare portati anche su altri OS…i loro in visual basic non girano piu’ neppure in windows seven…questa è microsoft…solo profitto…

    Veramente i vecchi programmi girano ancora. Poi se ti riferisci al fatto che non possono essere portati su VB.NET, è un altro discorso.

    state alla larga da java e .net se non volete buttare tempo e danaro…
    il C ad oggi non mi mai tradito…

    Se avessi dovuto usare il C per quello che ho fatto negli ultimi anni, mi sarei già sparato da un pezzo.

    @Nessuno:

    E allora parliamo di quello che stavo giusto dicendo, che la “potenza” del linguaggio non è misurabile se non tenendo conto della sua implementazione.

    Io dico che la “potenza” del linguaggio riguarda la sua implementazione.

    Nel tuo articolo questa cosa non è chiara, almeno per me, dato che parli di “potenza” di un linguaggio prima di fare riferimento al concetto di implementazione che nomini solo alla fine.

    Perché ho introdotto il concetto (comune, non oggettivo) di “potenza” all’inizio dell’articolo in quanto funzionale allo scopo dello stesso. Mi serviva per arrivare poi, man mano, a spiegare perché bisogna parlare di linguaggio da una parte, e implementazione dall’altro.

    Non a caso ho messo sempre la parola fra virgolette, perché era un invito a prenderlo con le molle.

    Il linguaggio ASM è una rappresentazione 1:1 del linguaggio macchina. Ovviamente posso fare un macro assembler che estrae parte dell’HW (o delle funzionalità) e un ASM che non è completo rispetto alle istruzioni che esistono. Stai ancora confondendo il linguaggio con la sua implementazione.

    Assolutamente no, e proprio perché l’assembly non è affatto una rappresentazione 1:1 del linguaggio macchina, come avevo già spiegato chiaramente nell’articolo.

    L’esempio del codice assembly 8008/8080/8085 tradotto tranquillamente per l’8086, che era un processore completamente diverso a livello di ISA, è lampante allo scopo.

    Se vuoi ti faccio gli esempi di assemblatori che mappano 1:1 le istruzioni del micro senza aggiunte e quelli che usano macro istruzioni per semplificare la ripetizione di micro sequenza ripetitive.

    Fammeli pure.

    Se la Intel ha deciso che la sua ISA deve essere ridondante ma il linguaggio ASM no, è una scelta di implementazione del linguaggio ma non cambia assolutamente nulla quali siano le istruzioni disponibili al programatore

    Certe scelte sono in realtà obbligate, e se ci sono delle ridondanze non è stato per un capriccio di Intel, ma un’esigenza concreta. Basti vedere, appunto, il caso dell’istruzione che ho fornito prima (ma anche per altre vale lo stesso).

    Inoltre è anche Intel che ha deciso sintassi e semantica del linguaggio assembly di 8086 & fratelli.

    (averne 10 che fanno la stessa identica cosa e usarne una di queste arbitrariarmente non cambia nulla a livello di programmazione).

    E invece cambia, eccome. Perché, tanto per fare un esempio, le istruzioni hanno dimensione diversa, e questo implica già una differenza di spazio. Nell’8086 comporta anche una differenza in termini di velocità d’esecuzione.

    La doppia astrazione a cui mi riferivo, e che non sembra che sia stata compresa la si vede in questo esempio: ammettiamo che tu riesca a fare un programma in C# che bypassa completamente le limitazioni imposte dal framework in cui gira. Proviamo a fare un driver per Linux, o anche direttamente per un qualsiasi micro senza OS, e vediamo se riesci a fare le stesse cose che io faccio in C. Magari preferisci Java al posto di C#. O Python, o qualsiasi altro linguaggio che è completamente pensato per essere astratto dall’HW su cui gira.

    Quello che ti sfugge è che anche il C è stato pensato per astrarsi dall’hardware, come ho spiegato nell’articolo. Tant’è che non è nemmeno definito cosa succede nel casting fra un intero e un puntatore, o viceversa, se non in un solo caso (da 0 a NULL; e NULL NON vuol dire locazione zero).

    Inoltre nessuno m’impedisce di tirare fuori un’implementazione diversa per i linguaggi che hai citato e che mi permetta di fare quanto hai scritto, compreso anche realizzare un s.o. (e, ribadisco, ne esistono già).

    E’ palese che le funzionalità esposte dal C/C++ sono completamente diverse (e più complete) da quelle che sono esposte da questi altri linguaggi.

    Non mi sembra che il C++ abbia roba come le inner function, le funzioni anonime, i generatori, le generator expression, le list comprehension, le metaclassi, ecc. ecc. ecc. Pertanto è un linguaggio “incompleto”.

    Io parlerei, invece, di linguaggio con un target diverso, e che espone funzionalità adatte allo scopo.

    Sebbene in C tu dica che non vi sia una regola che definisca come il linguaggio debba accedere ad una locazione precisa di memoria non toglie il fatto che il linguaggio lo permetta.

    Lo permette la particolare implementazione, non il linguaggio. E’ questa la chiave di lettura dell’articolo.

    In Turbo Pascal era addirittura possibile definire una variabile specificando direttamente il suo indirizzo. Ma nel draft ISO del Pascal (o dell’ObjectPascal) non v’è nulla del genere.

    In Java non si può. Qualsiasi costrutto tu tenti di fare con tale sintassi, non ci riuscirai mai.

    Per quanto già detto sopra, non vedo proprio perché no.

    Semplicemente perchè il linguaggio non è pensato per fare quello che fa il C/C++.

    Nemmeno in C o C++ puoi fare quanto hai detto prima. Draft alla mano.

    Anzi, all’opposto è pensato per evitare che alcune cose possibili con questi linguaggi siano implementabili (aritmetica dei puntatori per esempio).

    Di default è così, infatti. Ma si può sempre realizzare un’implementazione che lo permetta.

    Cosa che ad alto livello è caldamente sconsigliabile usare, ma a basso livello è linfa vitale e un linguaggio che ne impedisce l’uso semplicemente non è adatto a fare il basso livello.

    Guarda, già prima del C esistevano linguaggi coi puntatori, ma senza l’aritmetica dei puntatori, e i sistemi operativi li realizzavano lo stesso.

    P.S. Per quotare devi usare il tag XML blockquote.

  • # 15
    Nessuno
     scrive: 

    Di default è così, infatti. Ma si può sempre realizzare un’implementazione che lo permetta.

    Abbiamo 2 idee diverse di cosa sia un linguaggio di programmazione: voglio vedere come fai ad accedere ad una locazione di memoria fissa in Java senza modificare la sintassi dello stesso.
    In C anche se le regole per farlo non sono definite la sintassi lo permette senza alcun problema. In Java vorrei vedere un esempio scritto.

    Le funzionalità che hai elencato che mancano al C++ io non le ritengo “più potenti” di quanto sia in grado di fare il C++ con la sua capacità di accedere in maniera semplice all’HW. Sono estensioni che hanno un uso pratico limitato solo ad alcuni contesti, sempre ad alto livello. Non ti permettono di scrivere un OS su un microcontrollore.

    Io qui non sto cercando di sbandierare la supriorità di un linguaggio su un altro, sia ben chiaro. Sto solo dicendo che la definizione di potenza di un linguaggio è su due livelli: la prima deriva dalla sua sintassi + semantica, la seconda dalla sua implementazione.
    Il C/C++ non ha limiti per il basso livello in nessuna delle due. Altri linguaggi che sono stati nominati li hanno in entrambe. Ed è ovvio che un linguaggio che non permette di fare accesso alla memoria in maniera assoluta non è in grado di fare certe cose. Mentre se ad un linguaggio mancano delle funzionalità di alto livello, essendo come più volte detto equipotenziali, si può sempre, con maggior sforzo, ottenerle anche con linguaggi che non le prevedono di default.

    E invece cambia, eccome. Perché, tanto per fare un esempio, le istruzioni hanno dimensione diversa, e questo implica già una differenza di spazio. Nell’8086 comporta anche una differenza in termini di velocità d’esecuzione.

    Se mi dici che la funzionalità dell’istruzione breve è uguale a quella lunga, vuol dire che sceglierò quale istruzione usare secondo dei parametri di ottimizzazione generale: in questo caso la più corta. Ma non capisco questa testardaggine a portare un esempio di un ASM che tenta di mettere pezze ad una ISA mostro-frankenstein. Volendo sarebbe stato possibile inserire nel linguaggio le istruzioni i1, i2, i3 che fanno le stesse identiche cose ma usano le implementazioni diverse presenti nell’implementazione del micro. La scelta di non farlo è del particolare assembler Intel.
    Di mappatura 1:1 mi viene in mente l’ASM per SH2, Motorola 68000, PIC16 (i 18 hanno già delle istruzioni “macro”), DSP Texas, Fujitsu e NIOS.
    Per questi micro io ho usato assembler che non avevano capacità di astrarre un bel niente. Poi magari ce ne saranno anche delle implementazioni che aggiungono altre funzionalità, ma questo estende l’ASM “puro”. Altrimenti si arriva al paradosso che un compilatore C è un macro assembler.

    Quello che ti sfugge è che anche il C è stato pensato per astrarsi dall’hardware, come ho spiegato nell’articolo.

    Non mi sfuggono per niente le caratteristiche del C, ma diversamente dai linguaggi “moderni” di più alto livello ha mantenuto la capacità di connettersi all’HW in maniera semplice e diretta (funzionalità magari non necessarie a chi sviluppa un database, ma essenziali a chi fa sviluppo su sistemi senza OS con librerie già pronte).

    Appena posso provo a suggerire a qualche mio cliente di usare Java o C# compilato nativamente per sviluppare la futura centralina e vediamo cosa mi rispondono…

  • # 16
    Nessuno
     scrive: 

    Wow, sono riuscito a quotare! Grassie

  • # 17
    homero
     scrive: 

    a questo punto la discussione sembra dover essere spostata sul binomio (implementazioni, linguaggi)
    in quanto un linguaggio da solo non serve a nulla senza l’implementazione…

    partirei dalla differenza tra i vari runtime….
    quello nativo elf-exe, quello basato su framework, e quello interpretato….
    per quanto mi riguarda si puo’ scegliere C, python, java che sono tutti e tre multipiattaforma…
    .net lo relegherei all’oblio…

    un appunto sulle metaclassi…vengono dalla teoria dei linguagi unificati….ma nella pratica non introducono nulla di nuovo…
    forse e sottolineo forse…in progetti con multi programmatori possono mettere un po’ di ordine tra le classi….ma a parte questo….

  • # 18
    Cesare Di Mauro (Autore del post)
     scrive: 

    @Nessuno:

    Abbiamo 2 idee diverse di cosa sia un linguaggio di programmazione: voglio vedere come fai ad accedere ad una locazione di memoria fissa in Java senza modificare la sintassi dello stesso.
    In C anche se le regole per farlo non sono definite la sintassi lo permette senza alcun problema. In Java vorrei vedere un esempio scritto.

    Semplice: è sufficiente che l’implementazione metta a disposizione delle funzioni per leggere e scrivere dato un indirizzo.

    La stessa cosa che è stata fatta in C per leggere e scrivere le porte di I/O, visto che non c’è sintassi (da “piegare”) che tenga in questo caso.

    E questo vale per qualunque linguaggio, ovviamente.

    Le funzionalità che hai elencato che mancano al C++ io non le ritengo “più potenti” di quanto sia in grado di fare il C++ con la sua capacità di accedere in maniera semplice all’HW. Sono estensioni che hanno un uso pratico limitato solo ad alcuni contesti, sempre ad alto livello. Non ti permettono di scrivere un OS su un microcontrollore.

    L’hai scritto anche tu qui: il contesto di riferimento è la programmazione di basso livello.

    Esattamente come dicevo io prima: un linguaggio è “migliore” rispetto a un altro in base al problema da risolvere, e quindi al contesto applicativo.

    Per me Python è “migliore” di C++ perché espone le funzionalità che ho citato prima (e ce ne sarebbero di diverse altre, ovviamente). Ma sempre per quello che mi serve.

    Io qui non sto cercando di sbandierare la supriorità di un linguaggio su un altro, sia ben chiaro. Sto solo dicendo che la definizione di potenza di un linguaggio è su due livelli: la prima deriva dalla sua sintassi + semantica, la seconda dalla sua implementazione.

    Se rimaniamo rigorosamente fermi a sintassi e semantica, dubito che si possa parlare di “potenza”, visto che ci stiamo riferendo a un piano più astratto, dove appunto parlare di “basso livello” non ha praticamente senso.

    “Potenza” cosa significherebbe per te?

    Il C/C++ non ha limiti per il basso livello in nessuna delle due.

    Ma no, invece, a livello di linguaggio il basso livello offerto da C e C++ non esiste. Se ti leggi lo standard, è pieno di “implementation defined” nelle parti che più potrebbero riguardare il basso livello (i puntatori, appunto).

    Altri linguaggi che sono stati nominati li hanno in entrambe.

    Per quanto detto e per quello che interessa a te, l’unico punto di riferimento sono le implementazioni. E qui ci si può sbizzarrire, qualunque sia il linguaggio.

    Altrimenti non esisterebbe roba come PyPy, o Singularity, che ho già citato.

    Ed è ovvio che un linguaggio che non permette di fare accesso alla memoria in maniera assoluta non è in grado di fare certe cose.

    Fatta eccezione per codice macchina e assembly, nessun linguaggio lo permette.

    Mi faresti vedere in quale punto del draft dell’ISO C99 c’è scritto quello che hai affermato?

    Mentre se ad un linguaggio mancano delle funzionalità di alto livello, essendo come più volte detto equipotenziali, si può sempre, con maggior sforzo, ottenerle anche con linguaggi che non le prevedono di default.

    Attenzione, che sono due cose diverse e non si debbono confondere.

    Il fatto che tutti i linguaggi siano Turing completi vuol dire che sono capaci di risolvere gli stessi problemi.

    Ma NON vuol dire che, preso un linguaggio X, questo sia in grado di farlo utilizzando gli stessi strumenti di un altro linguaggio Y.

    Anche qui, per essere chiari, con Python posso usare le inner function, ma con C e C++ no, e puoi scrivere tutto il codice di supporto che vuoi, ma non ci riuscirai. Semplicemente perché questi linguaggi non hanno alcun supporto sintattico in grado di emulare questa funzionalità.

    Se mi dici che la funzionalità dell’istruzione breve è uguale a quella lunga, vuol dire che sceglierò quale istruzione usare secondo dei parametri di ottimizzazione generale: in questo caso la più corta.

    Non è sempre conveniente quest’approccio. Ad esempio potrei aver bisogno di incrementare il registro EAX, e di utilizzare l’istruzione più lunga per questione di padding e allineamento (chessò, a 32 bit) della successiva istruzione.

    Ma non capisco questa testardaggine a portare un esempio di un ASM che tenta di mettere pezze ad una ISA mostro-frankenstein. Volendo sarebbe stato possibile inserire nel linguaggio le istruzioni i1, i2, i3 che fanno le stesse identiche cose ma usano le implementazioni diverse presenti nell’implementazione del micro. La scelta di non farlo è del particolare assembler Intel.

    Perché l’assembly aveva uno scopo preciso: astrarre dal linguaggio macchina. Infatti nasconde dettagli che sono inutili per lo scopo per cui è nato e per cui si usa.

    Di mappatura 1:1 mi viene in mente l’ASM per SH2, Motorola 68000, PIC16 (i 18 hanno già delle istruzioni “macro”), DSP Texas, Fujitsu e NIOS.

    No, anche i 68000 hanno degli alias. Inoltre Motorola definì per l’assembly delle pseudo istruzioni, tipo push e pop. ;)

    Gli altri non li conosco e non mi posso esprimere.

    Per questi micro io ho usato assembler che non avevano capacità di astrarre un bel niente. Poi magari ce ne saranno anche delle implementazioni che aggiungono altre funzionalità, ma questo estende l’ASM “puro”.

    In questi casi bisogna guardare all’assembler fornito dalla casa madre. E’ quello che definisce lo standard per il linguaggio assembly dello specifico processore.

    Se poi altri ne realizzano delle versioni “stripped down”, beh, sono un’altra cosa…

    Altrimenti si arriva al paradosso che un compilatore C è un macro assembler.

    Nessun paradosso. Bisogna attenersi sempre agli standard, che rappresentano il punto di riferimento.

    Non mi sfuggono per niente le caratteristiche del C, ma diversamente dai linguaggi “moderni” di più alto livello ha mantenuto la capacità di connettersi all’HW in maniera semplice e diretta (funzionalità magari non necessarie a chi sviluppa un database, ma essenziali a chi fa sviluppo su sistemi senza OS con librerie già pronte).

    Come già detto prima, il linguaggio è pensato per il contrario: astrarsi dall’hardware. Altrimenti non si spiegherebbero i continui rimandi alle implementazione del draft.

    D’altra parte se vuoi realizzare un linguaggio quanto più portabile possibile, devi necessariamente slegarti dai dettagli di basso livello.

    Esempio: mi dici, leggendo il draft ISO C99, quanto spazio occupa esattamente un char? 1 byte? 2 byte? Altro?

    Non lo troverai mai, per quello che ho detto.

    Appena posso provo a suggerire a qualche mio cliente di usare Java o C# compilato nativamente per sviluppare la futura centralina e vediamo cosa mi rispondono…

    Battuta che lascia il tempo che trova. Non esistono implementazioni adatte ADESSO. Il che non vuol dire che non possano esistere.

    @homero:

    a questo punto la discussione sembra dover essere spostata sul binomio (implementazioni, linguaggi)
    in quanto un linguaggio da solo non serve a nulla senza l’implementazione…

    partirei dalla differenza tra i vari runtime….
    quello nativo elf-exe, quello basato su framework, e quello interpretato….
    per quanto mi riguarda si puo’ scegliere C, python, java che sono tutti e tre multipiattaforma…
    .net lo relegherei all’oblio…

    Francamente non capisco cosa vorresti che si facesse.

    un appunto sulle metaclassi…vengono dalla teoria dei linguagi unificati….ma nella pratica non introducono nulla di nuovo…
    forse e sottolineo forse…in progetti con multi programmatori possono mettere un po’ di ordine tra le classi….ma a parte questo….

    Non mi pare che sia la stessa cosa. Di seguito ti riporto un po’ di link utili a comprendere la metaprogrammazione in Python:

    http://www.ibm.com/developerworks/linux/library/l-pymeta.html
    http://gnosis.cx/publish/programming/metaclass_1.html
    http://tim.oreilly.com/pub/a/python/2003/04/17/metaclasses.html

    Sono un po’ datati, ma permettono di comprendere bene l’argomento (che risulta un po’ ostico).

  • # 19
    homero
     scrive: 

    http://it.wikipedia.org/wiki/Unified_Modeling_Language

    le metaclassi sono state concepite da questo mostro perverso….che si studia in ingegneria del software e produrrà programmatori di 4° ordine…

    essenzialmente un linguaggio compilato con runtime come exe e elf…in genere si basa su librerie o dialoga direttamente con l’OS….
    un linguaggio basato su virtual machine dialoga con la virtual machine e quindi ha uno startup diverso da quello eseguibile direttamente…
    il linguaggio interpretato dialoga con l’interprete che ne verifica la sintassi e tutto quanto apre i link con eventuali librerie e con il sistema operativo…

    allora si scrive un listato in C in python e in java di poche righe di codice ma non banale….e si confronta il modo in cui l’idea del programmatore tradotta nei vari linguaggi viene interpretata dalle varie implementazioni….tenendo in considerazione la portabilità l’espandibilità e le dipendenze…infatti queste 3 caratteristiche sono in genere quelle più importanti per un software….che vengono prima delle prestazioni…

  • # 20
    Cesare Di Mauro (Autore del post)
     scrive: 

    http://it.wikipedia.org/wiki/Unified_Modeling_Language

    le metaclassi sono state concepite da questo mostro perverso….che si studia in ingegneria del software e produrrà programmatori di 4° ordine…

    Sei completamente fuori strada. UML è molto recente, mentre il concetto di metaclasse risale al 1980, essendo stato introdotto con questo linguaggio, come puoi leggere qui: http://en.wikipedia.org/wiki/Smalltalk

    Sempre riguardo alle metaclassi: http://en.wikipedia.org/wiki/Metaclass Da notare che gli esempi sono in Python e SmallTalk.

    Poi su UML sono dell’idea che sia uno strumento inutile e dispersivo. Ben lontano dalla realtà per un programmatore.

    essenzialmente un linguaggio compilato con runtime come exe e elf…in genere si basa su librerie o dialoga direttamente con l’OS….
    un linguaggio basato su virtual machine dialoga con la virtual machine e quindi ha uno startup diverso da quello eseguibile direttamente…

    Parliamo sempre di implementazione, perché quello che hai scritto vale anche per gli interpreti C.

    il linguaggio interpretato dialoga con l’interprete che ne verifica la sintassi e tutto quanto apre i link con eventuali librerie e con il sistema operativo…

    OK

    allora si scrive un listato in C in python e in java di poche righe di codice ma non banale….e si confronta il modo in cui l’idea del programmatore tradotta nei vari linguaggi viene interpretata dalle varie implementazioni….tenendo in considerazione la portabilità l’espandibilità e le dipendenze…infatti queste 3 caratteristiche sono in genere quelle più importanti per un software….che vengono prima delle prestazioni…

    Qui http://shootout.alioth.debian.org/ trovi degli esempi di problemi che sono stati risolti nei diversi linguaggi, per cui se ne possono prendere per quello che chiedi tu.

    Ma non mi è chiaro cos’è che si dovrebbe verificare.

  • # 21
    homero
     scrive: 

    il link che mi hai dato non è propriamente quello che intendo…

    in pratica si scrive un programma in cui sono presenti una discreta quantità di costrutti e funzioni presenti nei tre linguaggi e l’uso delle librerie standard che sono per cosi’ dire simili tra i tre linguaggi…

    quindi if,for, do-while,math function, memory allocation,file access, console input/output…
    l’algoritmo puo’ essere una procedura che prevede un ordinamento diciamo un quick sort o una selezione di una base di dati eliminare le stringhe che iniziano con una determinata lettera o eliminare i numeri che sono primi in una sequenza di numeri interi casuali….diciamo 10mb di base di dati minimo….questi vengo caricati da disco e riscritti sul disco in forma ordinata e selezionata…
    dopo di che confrontiamo la velocità di esecuzione, la stabilità, la trasportabilità del codice tra un OS e un’altro e qualunque cosa ci viene in mente….la grandezza dell’eseguibile…

    avendo cura di scegliere per ciascun linguaggio la migliore implementazione presente sul mercato open source…
    quindi per il c GCC per linux o visual C++ express per windows per java la vm di oracle etc etc etc…

    potremmo avere delle sorprese….magari java è piu’ stabile e veloce…
    bisogna ovviamente utilizzare le peculiarita di ciascun linguaggio se queste migliorano le prestazioni de nostro bench….
    credo che in tutto saranno un 300 righe di codice al massimo….
    quindi tutto sommato fattibile..
    ovviamente lavoro permettendo mi rendo conto che forse ci vorranno 2 o 3 mesi….o magari una settimana….dipende ovviamente dal tempo che uno ha a disposizione…

    il passo successivo è ampliare la base di dati fino a che il software non collassa…..magari con il C riesci a gestire 1gb di dati….con java 100mb….con python 30mb…..non lo sappiamo a priori…

  • # 22
    Cesare Di Mauro (Autore del post)
     scrive: 

    Per quello che chiedi il link che ho fornito va benissimo, ed è, anzi, ben più ricco perché offre diversi scenari.

    Quanto alle prestazioni, mi sembrava di aver capito che t’interessava ben altro prima:

    allora si scrive un listato in C in python e in java di poche righe di codice ma non banale….e si confronta il modo in cui l’idea del programmatore tradotta nei vari linguaggi viene interpretata dalle varie implementazioni….tenendo in considerazione la portabilità l’espandibilità e le dipendenze…infatti queste 3 caratteristiche sono in genere quelle più importanti per un software….che vengono prima delle prestazioni…

    Mi sono permesso di evidenziare la parte incriminata.

    Come mai adesso hai cambiato idea?

  • # 23
    homero
     scrive: 

    mi sono reso conto che forse era troppo complesso quello avevo proposto…quindi ho abbassato il tiro (sbagliando) proponendo un bench facile da fare e che magari ogni utente poteva adattare al proprio linguaggio preferito o provarlo sul suo pc…
    i link che mi hai dato è molto interessante, ma i linguaggi non compilati in codice oggetto quindi java python et similia ne escono con le ossa rotte….e questo sicuramente è inglorioso…

  • # 24
    homero
     scrive: 

    ho visto alcuni bench scritti in C++ e devo dire che sono scarsamente leggibili…quelli in C sono invece abbastanza lineari…ma qui dipende dal programmatore piuttosto che dal linguaggio…alcuni si divertono a scrivere listati illeggibili….

  • # 25
    Nicola
     scrive: 

    ottimo articolo, almeno si è fatto il punto sulla differenza tra linguaggio ed implementazione.

    Per quanto riguarda la vicinanza all’hardware del C/C++, beh, già la presenza di heap e stack la dice lunga sulla sua natura “hardwarecentrica”

  • # 26
    Cesare Di Mauro (Autore del post)
     scrive: 

    @Nicola. Grazie. :)

    Comunque il concetto di stack e heap trascende l’hardware; ad esempio anche Python per allocare memoria (internamente, s’intende) utilizza l’heap (come tutte le implementazioni dei linguaggi, suppongo), mentre per le funzioni le variabili locali e quelle temporanee stanno in uno stack, sebbene questi sia allocato sempre nell’heap.

    @homero: però i benchmark non sono l’oggetto dell’articolo. Qui, in particolare, stai provando a fare un confronto ponendo “potenza” = “prestazioni” (metto anche queste sotto virgolette, perché si potrebbe disquisire a lungo anche su di esse).

    Il tutto prendendo delle implementazioni, salvo poi tirare conclusioni sui linguaggi, che… rappresentano cose diverse, come penso sia ormai chiaro sia dall’articolo che dai numerosi commenti.

    E che si tratti ti implementazioni e basta, lo dimostrano benchmark come quelli multicore, dove viene usata la libreria dei pthreads per il C, che non fa parte di quella standard per questo linguaggio, né tanto meno è disponibile per tutti i sistemi operativi…

    A parte questo, non mi sembra che Java sfiguri di fronte a C; anzi, ha prestazioni comparabili (non parliamo di ordini di grandezza diversi). Dovresti chiederti, piuttosto, come mai in alcuni benchmark ha prestazioni superiori al C (che, nell’immaginario collettivo, dovrebbe essere il non-plus-ultra in termini puramente “velocistici”). ;)

    Quanto a Python, non è nato né ha mai preteso di competere con linguaggi come il C in termini prestazionali. La sua “potenza” (ma ormai penso sia il caso di parlare di “potenzialità” o, meglio ancora, di “caratteristiche”; troppe ambiguità sorgono con l’altra parola) sta in ben altro, e i consensi e il suo continuo crescere in termini di adozione da parte dei programmatori ne sono un evidente segno. 8-)

    Infine sui sorgenti C++, beh, se non ti piacciono e sei in grado di fornirne di “migliori” (ma con prestazioni almeno equivalenti a quelle attuali), puoi benissimo fornirglieli tu. Sono aperti alle modifiche dei sorgenti (trovi contributi di parecchie persone).

  • # 27
    Nicola
     scrive: 

    ciao cesare infatti volevo sottolineare quest’aspetto, il mio riferimento ad heap e stack era alquanto provocatorio :-). Nello standard C/C++(visto che stavate parlando di questi linguaggi) non si menzionano Heap e Stack, da NESSUNA parte.Però Heap e Stack(non le relative strutture dati, intendiamoci) sono parole entrate nell’uso comune, derivate dal fatto che una FAMOSISSIMA implementazione dello standard C/C++ usa questi termini per indicare i due pool(logici) di memoria che essa utilizza e gestisce(in rete si trovano discussioni molto accese su questo aspetto :-)). In termini brevi, volevo sottolineare il fatto che linguaggio ed implementazione, benchè intimamente legati, a volte prendono “strade” un pochino diverse. E siccome tante volte non si coglie la differenza, per me Abissale, che passa tra definizione del linguaggio e sua implementazione, le varie sfumature si perdono, portando a farsi idee non dico sbagliate, ma comunque un po’ “confuse” sull’argomento. A volte si tende anche a confondere l'”hardware” con l'”Architettura di Von Neumann” e quindi a pensare che il tal linguaggio è più vicino all’hardware di un altro.

  • # 28
    Nessuno
     scrive: 

    Battuta che lascia il tempo che trova. Non esistono implementazioni adatte ADESSO. Il che non vuol dire che non possano esistere.

    No, non lascia il tempo che trova. Dopo 15 anni di Java e 10 di .NET semplicemente è la conferma che questi tipi di linguaggi sono decisamente inappropriati per questo tipo di lavoro.
    Ritornando al problema delle “inner function” come detto, non lo prescrive il dottore di utilizzarle. Probabilmente faciliteranno anche lo sviluppo in certi aspetti, ma usando le caratteristiche base del C++ è possibile ottenere alla fine gli stessi risultati. Ma ripeto, il viceversa rispetto al basso livello non esiste.
    Il fatto che il draft non includa come deve essere fatto l’accesso a basso livello è ovviamente parte del fatto che il linguaggio è astratto rispetto alla sua implementazione. Ma il linguaggio stesso non impedisce di suo di fare certe operazioni secondo come sono previste dall’implementazione. Infatti non conosco nessuna implementazione di C/C++ che non permetta di fare accesso ad una locazione di memoria assoluta (che sia virtuale o meno, ma quello dipende dall’OS più che dall’implementazione del linguaggio).
    In Java il linguaggio non prevede certi tipi di operazioni, anzi le impedisce, e per tanto qualsiasi sia l’implementazione che uno voglia fare di tale linguaggio queste cose non si possono fare. Non c’è molto da girar il can per l’aia in questo caso. Sto sempre aspettando un esempio di codice Java che permetta di fare accesso ad una locazione assoluta indipendentemente dalle limitazioni della VM.
    Ma non c’è solo Java. Tutta la schiera di linguaggi interpretati non lo possono fare semplicemente perché non sono pensati per farlo. In REBOL è possible usare qualsiasi periferiche che l’interprete è in grado di pilotare. Per cui leggere e scrivere da seriali, su file o TCP/IP è di una semplicità immensa. Farlo in C/C++ in confronto sarebbe come cavarsi gli occhi per lo sforzo necessario. D’altronde però con il Rebol se vuoi pilotare una qualsiasi periferica che l’interprete non supporta sei finito. Il linguaggio stesso prevede 2 livelli per le funzioni (3 per la verità, ma la mezzazine è la modalità ad alto livello implementata nel basso): alto livello disponibile liberamente al linguaggio anche per le modifiche, basso livello non modificabile e assolutamente “cablato” alla specifica implementazione dell’interprete (che inizialmente girava su qualcosa come 20 diverse architetture).

    Il linguaggio è potentissimo, un po’ criptico ma decisamente particolare potendo usare dati e codice come fossero la stessa cosa. Però anche se puoi fare un compilatore C/PHP/Python/Vattelapesca con tale linguaggio, rimane limitato di suo ad un contesto di alto livello.
    Quindi sono io che ti faccio la domanda, dato che l’articolo lo hai scritto tu: che cosa è la “potenza” di un linguaggio? Il fatto di permettere ad una scimmia di scrivere codice funzionante o di permettere di ottenere funzionalità che altrimenti non sarebbero possibili?

  • # 29
    Cesare Di Mauro (Autore del post)
     scrive: 

    @Nessuno:

    No, non lascia il tempo che trova. Dopo 15 anni di Java e 10 di .NET semplicemente è la conferma che questi tipi di linguaggi sono decisamente inappropriati per questo tipo di lavoro.

    Che ci siano linguaggi più adatti di altri per determinati scopi l’ho scritto già nell’articolo, e ribadito nei commenti.

    Per il resto se cerchi * operating system, con * = Java oppure C#, troverai qualcosa che dovrebbe interessarti.

    Fermo restando che anche se non esiste un solo s.o. scritto in quei linguaggi, nulla toglierebbe alla possibilità di realizzarne. Quel che conta è il potenziale.

    Ritornando al problema delle “inner function” come detto, non lo prescrive il dottore di utilizzarle.

    Il dottore può prescriverle soltanto per i linguaggi che le supportano a livello sintattico. Gli altri sono esclusi.

    Probabilmente faciliteranno anche lo sviluppo in certi aspetti, ma usando le caratteristiche base del C++ è possibile ottenere alla fine gli stessi risultati.

    Questo è quello che ho sempre affermato fin dall’articolo.

    D’altra parte le caratteristiche di un linguaggio incidono sul suo livello di produttività; in base ai problemi che si devono risolvere, ovviamente.

    Ma ripeto, il viceversa rispetto al basso livello non esiste.

    Non vedo perché.

    Il fatto che il draft non includa come deve essere fatto l’accesso a basso livello è ovviamente parte del fatto che il linguaggio è astratto rispetto alla sua implementazione.

    Ottimo. Almeno questo punto lo abbiamo fissato.

    Ma il linguaggio stesso non impedisce di suo di fare certe operazioni secondo come sono previste dall’implementazione. Infatti non conosco nessuna implementazione di C/C++ che non permetta di fare accesso ad una locazione di memoria assoluta (che sia virtuale o meno, ma quello dipende dall’OS più che dall’implementazione del linguaggio).

    Non può farlo perché è un’astrazione, appunto.

    In Java il linguaggio non prevede certi tipi di operazioni, anzi le impedisce, e per tanto qualsiasi sia l’implementazione che uno voglia fare di tale linguaggio queste cose non si possono fare. Non c’è molto da girar il can per l’aia in questo caso. Sto sempre aspettando un esempio di codice Java che permetta di fare accesso ad una locazione assoluta indipendentemente dalle limitazioni della VM.

    L’avevo spiegato già prima, e si può usare esattamente lo stesso modello che ha permesso al C di leggere e scrivere le porte di I/O (per i processori che prevedono questa separazione dell’address space), pur non avendo nessun supporto sintattico “piegabile” (nel senso di utilizzabile) allo scopo.

    Basta aggiungere delle apposite funzioni, a cui passare l’indirizzo (e il dato, per la scrittura).

    In Turbo Pascal Borland preferì introdurre due array speciali, Mem e Port, rispettivamente per accedere a una qualunque locazione di memoria e porta di I/O. Perché nemmeno il Pascal (come linguaggio standard) ovviamente permetteva l’accesso diretto a queste risorse.

    Ogni implementazione di qualunque linguaggio può scegliere il modo che ritiene più comodo / opportuno per aggiungere ALTRE caratteristiche al linguaggio (standard).

    Ma non c’è solo Java. Tutta la schiera di linguaggi interpretati non lo possono fare semplicemente perché non sono pensati per farlo. In REBOL è possible usare qualsiasi periferiche che l’interprete è in grado di pilotare. Per cui leggere e scrivere da seriali, su file o TCP/IP è di una semplicità immensa. Farlo in C/C++ in confronto sarebbe come cavarsi gli occhi per lo sforzo necessario. D’altronde però con il Rebol se vuoi pilotare una qualsiasi periferica che l’interprete non supporta sei finito. Il linguaggio stesso prevede 2 livelli per le funzioni (3 per la verità, ma la mezzazine è la modalità ad alto livello implementata nel basso): alto livello disponibile liberamente al linguaggio anche per le modifiche, basso livello non modificabile e assolutamente “cablato” alla specifica implementazione dell’interprete (che inizialmente girava su qualcosa come 20 diverse architetture).

    Il linguaggio è potentissimo, un po’ criptico ma decisamente particolare potendo usare dati e codice come fossero la stessa cosa. Però anche se puoi fare un compilatore C/PHP/Python/Vattelapesca con tale linguaggio, rimane limitato di suo ad un contesto di alto livello.

    Per le implementazioni che ci sono al momento, è così. Ma da ciò non puoi generalizzare passando al linguaggio.

    Quindi sono io che ti faccio la domanda, dato che l’articolo lo hai scritto tu: che cosa è la “potenza” di un linguaggio? Il fatto di permettere ad una scimmia di scrivere codice funzionante o di permettere di ottenere funzionalità che altrimenti non sarebbero possibili?

    Per me è il permettere di risolvere più o meno agevolmente determinate classi di problemi.

  • # 30
    Nessuno
     scrive: 

    Per me è il permettere di risolvere più o meno agevolmente determinate classi di problemi.

    Allora non è misurabile in senso assoluto. E’ solo una misura relativa al tipo di problema da risolvere E alla esperienza di colui che si accinge ad affrontare il problema.

    Non può farlo perché è un’astrazione, appunto.

    L’avevo spiegato già prima, e si può usare esattamente lo stesso modello che ha permesso al C di leggere e scrivere le porte di I/O (per i processori che prevedono questa separazione dell’address space), pur non avendo nessun supporto sintattico “piegabile” (nel senso di utilizzabile) allo scopo.

    Non può farlo perché la sintassi del linguaggio non lo prevede. Se non prevede certe funzionalità, come l’algebra dei puntatori, ti puoi inventare quello che vuoi, ma non riuscirai a ottenerla. Altrimenti avresti scoperto una falla nel modello originale del linguaggio che è previsto non te lo lasci fare.
    Usare funzioni che fanno uso di potenzialità di altri linguaggi è barare. Non riusciresti mai a scrivere in linguaggio Java una funzione che ti permette di scrivere/leggere in una locazione di memoria assoluta. Lo puoi fare con altri linguaggi e poi “wrapapre” tale funzionalità ad una normale funzione di libreria Java, ma stai semplicemente ammettendo che il linguaggio che usi non è in grado di fare qualcosa. E non è qualcosa che ti complica lo sviluppo o ti costringe ad un approccio diverso (tipo con o senza le inner functions, exceptions o multi ereditarietà etc…). Ti impedisce proprio di poter usare il linguaggio in determinati contesti.
    Posso decidere, quando sviluppo uno script per generare un qualsiasi documento (pagina web per esempio) se mi conviene usare una immensa pletora di linguaggi diversi, compresi C/C++, Java, PHP, Perl, Ruby, Phyton, Rebol o persino Labview.
    Trovo impossibile che qualcuno si ponga il problema se sia necessario considerare Java o il PHP come linguaggi potenziali per lo sviluppo di un driver. Assunto che ad ogni problema c’è sicuramente una accoppiata esperienza/linguaggio che sia migliore delle altre, esistono anche dei problemi dove questa accoppiata non esiste per niente anche se si pone il grado di esperienza del programmatore ad infinito.
    Questo dovrebbe dare l’idea di quello che significa “potenza del linguaggio”.

  • # 31
    Cesare Di Mauro (Autore del post)
     scrive: 

    @Nessuno:

    Allora non è misurabile in senso assoluto. E’ solo una misura relativa al tipo di problema da risolvere E alla esperienza di colui che si accinge ad affrontare il problema.

    Esattamente, e pensavo fosse chiaro dall’articolo. Non ha senso parlare di “potenza” per i linguaggi di programmazione: sono tutti equipotenti in quanto Turing-completi e ognuno coi propri punti di forza e di debolezza.

    Non può farlo perché la sintassi del linguaggio non lo prevede. Se non prevede certe funzionalità, come l’algebra dei puntatori, ti puoi inventare quello che vuoi, ma non riuscirai a ottenerla. Altrimenti avresti scoperto una falla nel modello originale del linguaggio che è previsto non te lo lasci fare.

    Il linguaggio prevede le chiamate a funzioni, e in particolare a funzioni statiche.

    Per cui è possibilissimo mettergli a disposizione un paio di funzioni allo scopo. Similmente alle tonnellate di funzioni che sono presenti nella libreria standard.

    Allo scopo non serve, tra l’altro, l’algebra dei puntatori: è sufficiente poter leggere o scrivere a precisi indirizzi di memoria. Per il resto, è sufficiente l’algebra degli interi.

    Facciamo una cosa, e tagliamo la testa al toro una volta per tutte. Rispondi a questa domanda: mi fai vedere come si fa in C a leggere e/o scrivere le porte di I/O dei processori che prevedono questo tipo di istruzioni per comunicare con le periferiche?

    Ovviamente puoi usare liberamente la sintassi del linguaggio come meglio ti aggrada, ma solo quella del C. Quindi niente assembly o codice macchina “in linea”.

    Usare funzioni che fanno uso di potenzialità di altri linguaggi è barare. Non riusciresti mai a scrivere in linguaggio Java una funzione che ti permette di scrivere/leggere in una locazione di memoria assoluta. Lo puoi fare con altri linguaggi e poi “wrapapre” tale funzionalità ad una normale funzione di libreria Java, ma stai semplicemente ammettendo che il linguaggio che usi non è in grado di fare qualcosa. E non è qualcosa che ti complica lo sviluppo o ti costringe ad un approccio diverso (tipo con o senza le inner functions, exceptions o multi ereditarietà etc…). Ti impedisce proprio di poter usare il linguaggio in determinati contesti.

    Premetto intanto che lo stesso, identico, concetto di “barare” è applicabile al C nel momento in cui è costretto a ricorrere all’assembly o al linguaggio macchina per accedere a funzionalità che gli sarebbero negate altrimenti.

    Anche qui, sempre per tagliare la testa al toro, ti faccio qualche domanda. Mi dici come posso effettuare una system call? E come posso abilitare o disabilitare gli interrupt? Come scrivere un interrupt handler? Come caricare la root page dell’MMU? Come accedere ai registri di debug? Alla CPUID? Come switchare da real mode al protected mode (per x86)? Ecc. ecc. ecc.: l’elenco potrebbe tranquillamente continuare.

    Detto ciò, quel che affermi non è vero anche per un altro motivo, che avevo già spiegato prima e riprendo. Ti sei chiesto come faccia PyPy a generare virtual machine Python essendo il suo codice scritto in (R)Python? Ti sei chiesto come faccia a generare indifferentemente codice C, codice macchina PowerPC, bytecode Java o IL CLR/.NET, pur essendo scritto tutto in (R)Python?

    La risposta è semplice: perché dal puro esercizio di stile di scrivere un linguaggio nello stesso linguaggio si è passati alla pratica… E questo vale per qualunque linguaggio.

    Giusto per essere chiari, in Python potrei scriverti anche il bootloader dell’MBR che viene richiamato dal BIOS e che provvede poi a far partire il s.o..

    Posso decidere, quando sviluppo uno script per generare un qualsiasi documento (pagina web per esempio) se mi conviene usare una immensa pletora di linguaggi diversi, compresi C/C++, Java, PHP, Perl, Ruby, Phyton, Rebol o persino Labview.
    Trovo impossibile che qualcuno si ponga il problema se sia necessario considerare Java o il PHP come linguaggi potenziali per lo sviluppo di un driver. Assunto che ad ogni problema c’è sicuramente una accoppiata esperienza/linguaggio che sia migliore delle altre, esistono anche dei problemi dove questa accoppiata non esiste per niente anche se si pone il grado di esperienza del programmatore ad infinito.
    Questo dovrebbe dare l’idea di quello che significa “potenza del linguaggio”.

    E allora per quanto detto prima puoi metterti l’anima in pace.

    Primo perché lo stesso C è costretto a ricorrere ad altri linguaggi di più basso livello per funzionalità che gli sarebbero impossibili da usare altrimenti (dico: sfruttando soltanto la sua sintassi). E non stiamo parlando di pura teoria: è quel che si fa già quando si scrivono dei s.o.; una parte, anche piccolissima, rimane in assembly o linguaggio macchina.

    Secondo perché è possibile, con un linguaggio, generare tutto quello che serve per interfacciarsi direttamente con l’hardware.

    Spero di essere stato chiaro questa volta.

  • # 32
    Nicola
     scrive: 

    Nessuno, senza offesa, ma forse tu non riesci proprio a scindere il concetto di definizione di linguaggio da quello di implementazione. Per farti un esempio con Java, dato che lo hai tirato in ballo di frequente, chi ti vieta di prendere un listato Java e compilarlo per farlo “girare” nativamente sulla macchina per la quale lo hai compilato, senza implementare nessuna virtual machine? in questo modo puoi accedere a TUTTO l’hardware che vuoi, con qualsiasi linguaggio. Ti piacciono tanto i puntatori? lo sai che in Java qualsiasi riferimento ad un Oggetto è un puntatore? il metodo toString, quando non ridefinito, mostra semplicemente l’indirizzo dell’oggetto sul quale è invocato. Quando fai == o invochi il metodo equals “non ridefinito” confronti gli oggetti in base all’indirizzo in memoria. Tutti gli indirizzi sono relativi allo spazio di memoria della virtual machine, ma come dicevo prima basta fornire una implementazione diversa, et voilà…..se vuoi l’algebra dei puntatori la puoi implementare ugualmente, magari non esplicita come in C/C++(a meno di usare i tipi base come pseudo-puntatori) ma puoi averla ugualmente….Non fissarti in testa inutili pregiudizi su un qualsiasi linguaggio, e spero tanto che tu non sia uno di quelli che pensa che programmare “a basso livello” è più da fighi, mentre gli sfigati usano Java e C#…Scusami se mi sono permesso di farti questo appunto.

  • # 33
    Cesare Di Mauro (Autore del post)
     scrive: 

    La questione verrà risolta con le risposte alle domande che ho fatto nel precedente commento.

    Ma è già sufficiente la prima (quella sulle porte di I/O) per metterci una pietra tombale sopra. ;)

  • # 34
    khelidan
     scrive: 

    @Cesare

    Perchè non scrivi un articolo su RPython e sulle sue potenzialità, secondo me sarebbe interessante, anche per gli strenui difensori del C!

  • # 35
    Cesare Di Mauro (Autore del post)
     scrive: 

    Per adesso no, per due motivi.

    Il primo è che dovrei lavorarci un po’ per afferrare appieno quello che è in grado di fare come linguaggio “staticamente tipizzato”.

    Il secondo è che RPython è una versione ridotta di Python, e quest’ultimo è un linguaggio già noto. RPython, nasce e muore con e per PyPy, e la comunità non ha nessuna intenzione di farlo diventare un linguaggio “mainstream” (quindi separato da PyPy).

    Però ci penso sù. Vedremo. :)

Scrivi un commento!

Aggiungi il commento, oppure trackback dal tuo sito.

I commenti inseriti dai lettori di AppuntiDigitali non sono oggetto di moderazione preventiva, ma solo di eventuale filtro antispam. Qualora si ravvisi un contenuto non consono (offensivo o diffamatorio) si prega di contattare l'amministrazione di Appunti Digitali all'indirizzo info@appuntidigitali.it, specificando quale sia il commento in oggetto.