di  -  mercoledì 27 Aprile 2011

Il C non ha certo bisogno di presentazioni, essendo uno dei linguaggi che ha avuto più successo ed è ancora fra i più utilizzati, nonostante si avvicini ai 40 anni d’età. Complice il successo di Unix (scritto in C), si è fatto strada anche grazie al sua relativamente ridotta e succinta sintassi, e alcuni lo preferiscono al C++ proprio per questa ragione, nonostante quest’ultimo sia sostanzialmente un suo superset.

E’ stato utilizzato per lo sviluppo di applicazioni fra le più disparate, ma essendo considerato di medio-basso livello il suo campo d’elezione è la programmazione di sistema. Sistemi operativi (ovviamente) e driver sono usualmente scritti C, ma anche emulatori e macchine virtuali sono un naturale ambito d’utilizzo, per citare altri esempi molto noti.

Nonostante non sia fra i miei linguaggi preferiti, a causa della sua diffusione e della mia passione per le ultime due cose citate mi è capitato sovente di doverlo utilizzare. E poiché compilatore e VM di Python (che, al contrario, adoro) più diffusi sono scritti in C, il mio smanettarci (il progetto WPython è uno dei risultati di tale lavoro, di cui in futuro parlerò più approfonditamente) negli ultimi anni mi ha riportato a lavorare spesso in C (la notte mi è testimone).

Chiaramente più ci si lavora e più si prende coscienza dei pregi e dei difetti del linguaggio nella vita di tutti i giorni, confrontandosi con le esigenze che saltano fuori in progetti concreti. Studiare la sintassi e la semantica, insomma, è soltanto il primo, importante, passo di un lungo cammino che porterà al formarsi della tipica mentalità del programmatore di quel particolare linguaggio, nonché prendere atto delle sue problematiche e dei limiti.

M’era già capitato altre volte, ma con CPython (si chiama così la versione “mainstream” di Python) qualcuno abbastanza seccante è saltato nuovamente fuori, e riguarda la definizione di una gerarchia di “tipi” tramite delle strutture dati, con la quale modellare i tipi, appunto, utilizzati nella VM.

CPython ne definisce una moltitudine per diversi scopi, ma vedere in che modo ne è rappresentato qualcuno aiuterà a comprendere velocemente la problematica in oggetto. Prendiamo, ad esempio, il tipo più utilizzato: l’intero (con segno e a 32 o 64 bit, a seconda della piattaforma su cui gira). Esso è rappresentato da una precisa struttura:

typedef struct {
    PyObject_HEAD
    long ob_ival;
} PyIntObject;

Fin qui sembra tutto a posto e la definizione è pure molto semplice. ob_ival è il campo che racchiude il valore intero vero e proprio, ma PyObject_HEAD non ha certo l’aspetto di un campo. Infatti andando a vedere com’è definito salta fuori la sua vera natura:

/* PyObject_HEAD defines the initial segment of every PyObject. */
#define PyObject_HEAD			\
    _PyObject_HEAD_EXTRA		\
    Py_ssize_t ob_refcnt;		\
    struct _typeobject *ob_type;

Si tratta di una macro (la parte del nome in maiuscolo forniva già qualche indicazione in merito, data la generale convenzione di utilizzarlo per indicare tutto o parte dell’identificatore), che definisce un altro paio di campi: ob_refcnt è un valore intero (in realtà è un ssize_t) usato per tenere conto del cosiddetto reference counting, mentre ob_type è un puntatore a un’altra struttura che identifica il tipo a cui appartiene l’oggetto in questione.

_PyObject_HEAD_EXTRA, come possiamo intuire ormai, è un’altra macro:

#define _PyObject_HEAD_EXTRA

In pratica non definisce nulla. In realtà se risulta abilitata una particolare opzione, Py_TRACE_REFS (in modalità di debug), prende la seguente forma:

/* Define pointers to support a doubly-linked list of all live heap objects. */
#define _PyObject_HEAD_EXTRA		\
    struct _object *_ob_next;	\
    struct _object *_ob_prev;

Quindi ci sono ancora un altro paio di campi definiti per tenere traccia di tutte le istanze tramite una lista doppiamente concatenata.

In definitiva, “espandendo” le macro, un PyIntObject avrebbe la seguente struttura:

typedef struct {
    Py_ssize_t ob_refcnt;
    struct _typeobject *ob_type;
    long ob_ival;
} PyIntObject;

oppure, con Py_TRACE_REFS abilitato:

typedef struct {
    struct _object *_ob_next;
    struct _object *_ob_prev;
    Py_ssize_t ob_refcnt;
    struct _typeobject *ob_type;
    long ob_ival;
} PyIntObject;

Se il C permettesse di derivare una struttura da un’altra (o, viceversa, estendere una struttura esistente, a seconda di come la si voglia vedere), il problema sarebbe stato risolto in maniera molto più semplice, elegante, leggibile, e manutenibile:

typedef struct {
#ifdef Py_TRACE_REFS
    struct _object *_ob_next;
    struct _object *_ob_prev;
#endif
} _PyObject_HEAD_EXTRA;

typedef struct(_PyObject_HEAD_EXTRA) {
    Py_ssize_t ob_refcnt;
    struct _typeobject *ob_type;
} PyObject_HEAD;

typedef struct(PyObject_HEAD) {
    long ob_ival;
} PyIntObject;

Ovviamente con la possibilità di “rilassare” il passaggio di queste strutture senza ricorrere a cast (downcast, per la precisione). Ad esempio, per accedere ai campi “comuni” ob_refcnt e ob_type si fa ricorso alle seguenti macro:

#define Py_REFCNT(ob)    (((PyObject*)(ob))->ob_refcnt)
#define Py_TYPE(ob)      (((PyObject*)(ob))->ob_type)

di cui si potrebbe fare tranquillamente a meno, utilizzando direttamente la classica notazione ob->ob_refcnt e ob->ob_type dove servisse.

Non pensiate che si tratti di casi rari, perché l’esigenza di definire gerarchie di tipi oppure di avere delle strutture “base” per altre più complesse è, invece, abbastanza comune, persino in un s.o..

Ad esempio un elenco di processi, file, o di risorse in generale si potrebbe (e in genere si fa così) gestire tramite una lista che faccia uso di una struttura nodo “classica” a cui vengono poi aggiunte le informazioni specifiche relative a quella particolare informazione. Ovviamente il tutto riutilizzando le funzioni di inserimento, cancellazione, ricerca, ordinamento, ecc., di un nodo in una lista.

Sempre riguardo alle strutture, non so quante volte sarà capitato di accedervi per manipolare dei campi. Ad esempio:

static int
compiler_push_fblock(struct compiler *c, enum fblocktype t, basicblock *b)
{
    struct fblockinfo *f;
    if (c->u->u_nfblocks >= CO_MAXBLOCKS) {
        PyErr_SetString(PyExc_SystemError,
            "too many statically nested blocks");
        return 0;
    }
    f = &c->u->u_fblock[c->u->u_nfblocks++];
    f->fb_type = t;
    f->fb_block = b;
    return 1;
}

Qui è stato necessario utilizzare la variabile f di “appoggio” per evitare di scrivere linee di codice particolarmente lunghe. I pascaliani avrebbero fatto ricorso al costrutto with:

static int
compiler_push_fblock(struct compiler *c, enum fblocktype t, basicblock *b)
{
    with (c->u) {
        if (u_nfblocks >= CO_MAXBLOCKS) {
            PyErr_SetString(PyExc_SystemError,
                "too many statically nested blocks");
            return 0;
        }
        with (*u_fblock[u_nfblocks++]) {
            fb_type = t;
            fb_block = b;
            return 1;
        }
    }
}

Oppure, senza introdurre nuove keyword (per evitare problemi di compatibilità):

static int
compiler_push_fblock(struct compiler *c, enum fblocktype t, basicblock *b)
{
    c->u.{
        if (u_nfblocks >= CO_MAXBLOCKS) {
            PyErr_SetString(PyExc_SystemError,
                "too many statically nested blocks");
            return 0;
        }
        u_fblock[u_nfblocks++]-> {
            fb_type = t;
            fb_block = b;
            return 1;
        }
    }
}

In pratica si tratta di “aprire” un nuovo scope che “punti” all’interno della struttura dell’oggetto, lasciando al compilatore di smazzarsi il compito di referenziare correttamente tutti i campi e di apportare anche le ottimizzazioni che ritiene più opportune per velocizzare l’operazione.

Chi ha lavorato col C sa che si tratta di esigenze comuni; molto comuni.

Purtroppo il C è un linguaggio che si evolve molto lentamente, ma ciò non ha impedito, ad esempio, di introdurre il supporto ai numeri complessi con l’ISO C99. Non so quanto possa essere utile il supporto ai complessi, ma penso che il comitato dovrebbe prestare più attenzione a risolvere problemi che hanno un maggior impatto nella vita dei programmatori.

Ovviamente non si pretende di estendere il linguaggio infilando tutte le idee che in questi ultimi decenni sono saltate fuori. Il C ormai ha una sua “struttura”, una sua “filosofia”, ed è anche per questo che continua a essere apprezzato.

Credo, però, che qualche sistemata per migliorare l’uso di quanto già esista non possa che giovare, specialmente a chi ci lavora…

38 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
    Antonio Barba (TheKaneB)
     scrive: 

    Il C ormai è troppo vecchio per pensare di innovarlo in questo modo… Per risolvere il primo problema (struct) ormai è inutile sperimentare accrocchi di struct “avanzate” quando esiste già il C++.
    Per quanto riguarda il costrutto “with”, ci sono pro e contro. Da un lato semplifica la scrittura del codice, dall’altro lo rende meno leggibile e potenzialmente pericoloso perchè aumenta le possibilità di name clashing.
    Generalmente tendo a scrivere codice molto verboso, perchè lo scrivi normalmente 1 volta e poi lo devi rileggere (o altri lo devono rileggere) almeno altre 20 volte e capire al volo cosa succede nel codice.
    Analogamente tendo ad eliminare la clausula “using namespace” del C++ esattamente per lo stesso motivo, preferisco scrivere lo scope completo, che occupa un sacco di spazio ma elimina qualsiasi tipo di ambiguità quando quel codice verrà riletto da qualcun’altro.

  • # 2
    SamioSeven
     scrive: 

    Qualche limite indigesto nelle struct del C…

    OFF TOPIC ON

    Certo, Cesare, che il titolo messo così, si presta volentieri a varie interpretazioni… Poi leggendo il resto, si capisce bene.. :D

    OFF TOPIC OFF

    Complimenti per la tua professionalità :) Il C è davvero un buon linguaggio

  • # 3
    Giulio85
     scrive: 

    C++ è un orrore progettuale.

    Un linguaggio che ti spinge a separare l’header delle funzioni dall’implementazione, per cui ogni volta che devi aggiungere o modificare una funzione devi costantemente modificare anche l’header, perdendo tempo.

    Praticamente hanno implementato il concetto di interfaccia basato su file, una schifezza.

    Inoltre alcune implementazioni sfruttano questa informazione per decidere COME compilare il codice (VC++ ad esempio tende a compilare le eventuali funzioni di classe scritte nei file .h inline).

    Poi la sintassi intricata dei template, per cui per ogni santa funzione in una classe devi specificare che si tratta di una funzione template, quando già l’hai definito a livello di classe.

    Senza contare la miriade di ambiguità relative alla posizione della keyword “const”, specie quando abbinata ai puntatori.

    Senza contare una libreria standard che fa veramente pena e che andrebbe totalmente riscritta e fatta come cristo comanda (basterebbe prendere esempio dalla libreria Qt o Boost).

    Ho scritto codice C leggibile, in C++ devi evitare alcune caratteristiche del linguaggio se vuoi ottenere lo stesso risultato, un successo! :(

  • # 4
    Antonio Barba (TheKaneB)
     scrive: 

    @Giulio: gli orrori degli header files il C++ se li trascina dietro dal C ovviamente, tale padre tale figlio :-D
    Per quanto riguarda l’oscenità dei template concordo assolutamente. Preferisco il modo in cui sono implementati i “generics” nel Delphi, di gran lunga più pulito come sistema.
    Ciò non toglie che “espandendo”, per così dire, il C secondo me non si riuscirebbe a fare di meglio del C++, o dell’Objective-C, che purtroppo hanno deciso di portarsi dietro tutti i difetti del C.
    Il C ha tanti difetti, ma alla fine sono perdonabili perchè è un linguaggio piccolo, molto vicino alla macchina, ed ha un suo specifico campo di applicazione. Gli stessi difetti portati su un linguaggio di alto livello, come il C++, e addirittura amplificati (grazie alle n-mila cose in più che questo linguaggio implementa) semplicemente non sono tollerabili IMHO.
    Purtroppo lo conosco bene e l’ho usato tantissimo per lavoro, ma decisamente c’è di meglio per lavorare su progetti di alto livello. Invece se rimaniamo sul “suo” livello, il C rimane decisamente comodo e potente per scrivere firmware, parti time-critical di kernel, parti time-critical di emulatori, ecc… (e sopratutto a quel livello si presuppone che il programmatore sia bravo abbastanza da scavalcare egregiamente i difetti del linguaggio).

  • # 5
    iva
     scrive: 

    @Antonio: concordo con l’evitare “using namespace”, ho visto l’utilizzo da parte di alcuni programmatori (dotati di fantasia non comune!) anche .h, doh!

    @Giulio: va bene che il C++ non ti piace, pero’ alcune critiche non mi sembrano fondate:

    – “Inoltre alcune implementazioni sfruttano questa informazione per decidere COME compilare il codice (VC++ ad esempio tende a compilare le eventuali funzioni di classe scritte nei file .h inline).”
    Lo specifica lo standard del C++, paragrafo 7.1.2.3:
    “A function defined within a class definition is an inline function.”.
    E non capisco perche’ il “COME” sia un problema, se non precisi meglio.

    – “Senza contare la miriade di ambiguità relative alla posizione della keyword “const”, specie quando abbinata ai puntatori.”
    Con in puntatori non c’e’ una miriade di ambiguita’ anzi, non ne hai nemmeno una: dipende solo se usi const a sinistra o destra del *

  • # 6
    Giulio85
     scrive: 

    @iva:
    Non si capisce perché si debba generare una funzione inline a seconda di dove la scrivo.

    L’ambiguità riguardo al “const” è per chi legge il codice, che si deve ricordare che const assume un significato diverso a seconda di dove è posto.

    Non è che il C++ non mi piace, è che oggettivamente hanno fatto scelte molto ma molto discutibili.

  • # 7
    Giulio85
     scrive: 

    const int Var; /* Var is constant */
    int const Var; /* Ditto */
    int * const Var; /* The pointer is constant,
    * the data its self can change. */
    const int * Var; /* Var can not be changed. */

    Sintassi per nulla intuitiva.

    A questo punto se si vuole assicurare che un puntatore non venga modificato nell’ambito di un dato problema, meglio lasciare un puntatore standard e usare l’incapsulamento per gestire la cosa in maniera semplice, ma soprattutto comprensibile.

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

    Per tutti: l’articolo non vuol fare un confronto fra linguaggi, cosa che accende facilmente delle faide, e non era assolutamente mia intenzione.

    @Antonio: il C, come dici, è un linguaggio piccolo, che fa bene il suo lavoro negli ambiti che anche tu hai elencato. Ciò non toglie che, allo scopo, abbia qualche difetto che ho voluto riportare nell’articolo.

    Personalmente non è mia intenzione chiedere modifiche per “espansioni” del linguaggio o introdurre chissà che cosa (tra l’altro non sono nemmeno nella posizione di farlo :D), ma volevo sottolineare soltanto che qualche utile modifica agli strumenti che mette già a disposizione non sarebbe male. Tutto qui.

    Riguardo alla leggibilità, da pythonista è uno degli obiettivi che ricerco quando scrivo codice. La soluzione non sta né nel codice troppo verboso né in quello troppo spoglio/succinto.

    Uno strumento come il “with” (o l’operatore di scoping, se mi passi il termine, che mi sono inventato su due piedi) può essere usato per scrivere codice illeggibile o per facilitarne la lettura, a seconda di come viene usato.

    E’ un po’ come l’ereditarietà da più classi. In molti la considerano un errore e portatrice di problemi, ma è anche vero in alcuni casi riesce a risolvere in maniera elegante (e leggibile) alcuni problemi che con l’ereditarietà singola e/o da più interfacce richiederebbe degli accrocchi…

    Nello specifico, gli esempi che ho riportato mi sembrano abbastanza chiari già “a occhio”, col contesto delle variabili in gioco rilevabile molto velocemente anche senza avere davanti la definizione delle strutture.

    @SamioSeven: purtroppo non sono riuscito a trovare di meglio per il titolo. E ti assicuro che di tempo ne ho perso abbastanza. -_-

    Quasi quasi ci scrivo un articolo: “Dove perdono più tempo i programmatori?” :D

  • # 9
    iva
     scrive: 

    @Giulio:
    ok, sul “per nulla intuitiva” siamo d’accordo, sull'”ambigua” no, o sai come funziona il linguaggio che stai leggendo o fai un altro mestiere, sara’ l’italiano :)

    Per quanto riguarda la funzione inline a me non rimane difficile immaginare che quel codice venga compilato inline (visto che al massimo si “sporcano” i file include con funzioni semplici, tipo getter/setter).
    Ma non hai risposto la domanda: quale problema REALE ti crea il fatto che il compilatore faccia l’inlining a tua insaputa? (e tra parentesi il compilatore puo’ astenersi dal fare l’inlining anche se tu lo chiedi esplicitamente visto che e’ solo un’indicazione, non un ordine).
    Nell’esecuzione del programma cosa ti cambia semanticamente?
    A me personalmente puo’ dare fastidio nel debugging, ma in ogni tipo di codice ottimizzato fare debugging e’ comunque “black magic”…

  • # 10
    Marco
     scrive: 

    @iva
    “A me personalmente puo’ dare fastidio nel debugging”

    Ma anche no: in VC++ ad esempio le non standard __inline e __forceinline vengono (correttamente?) ignorate quando fai una build di debug, ma non solo.
    Semanticamente usando l’inline perdi l’incapsulamento, quindi è bene sapere esattamente cosa farà il compilatore; ci possono poi essere n motivi per cui l’indicazione non venga rispettata, fra cui la più ovvia che mi sovviene è che la funzione sia ricorsiva (visto che a compile time non è nota la profondità della ricorsione, che andrebbe definita a mano), oppure altrettanto ovviamente quando abbiamo funzioni di arietà variabile.
    Morale, è sempre bene dare un’occhiata al log del compilatore (-Winline nel GCC, non ricordo in VC) ;-)

  • # 11
    Antonio Barba (TheKaneB)
     scrive: 

    @iva: a parte il fatto che le funzioni inline hanno un concetto particolare di “indirizzo della funzione” (cosa per altro rilevante solo nel C, dove è comune usare puntatori a funzione), non mi sembra che ci siano ulteriori problemi di natura semantica. Semmai allungano il codice e questo potrebbe creare cache miss, alla faccia del presunto aumento di velocità dovuto all’inlining. Questo comunque è tutto un’altro problema, e va risolto usando un profiler, a prescindere dal linguaggio di programmazione utilizzato.

  • # 12
    Antonio Barba (TheKaneB)
     scrive: 

    @Cesare: si è chiaro il tuo intento, si tratta di riflessioni su cosa avrebbe potuto includere il C, roba a cui avrebbero dovuto/potuto pensare 40 anni fa, di certo non roba implementabile “oggi” :-)

  • # 13
    Giulio85
     scrive: 

    Non è che da fastidio l’inlining di per se, il problema è che il linguaggio mi obbliga a separare definizione ed implementazione.

    Se volessi includere tutto in un unico file devo tenere conto che il compilatore realizza (o potrebbe realizzare) funzioni inline, non su basi prestazionali o su comandi dell’utente, ma solo sulla semplice base che ho dichiarato una funzione in un posto piuttosto che in un altro.

    Il che mi sembra ancora più ridicolo considerando che C++ al giorno d’oggi viene considerato un linguaggio da “power user”, poiché dovrebbe dare maggiore controllo al programmatore.

    Comunque, considerando la faccenda del “const”, un linguaggio dovrebbe avere un minimo di coerenza verso se stesso IMHO, e secondo me quel modo di dichiarare un tipo in C++ è palesemente incoerente.
    Se dichiaro un tipo semplice la posizione di const non ha rilevanza, se dichiaro un puntatore si.

    Non è questione di conoscere il linguaggio, è questione che sono state fatte scelte più che discutibili.

  • # 14
    iva
     scrive: 

    @Marco: non voglio ora passare il pomeriggio su questo thread, ma no, non perdi affatto “l’incapsulamento”.
    L’inlining e’ un dettaglio dell’implementazione, non ha effetto sul significato semantico del sorgente.
    Il discorso sul debugging con codice inline era generico, non esiste solo Microsoft con i suoi OS e compilatori – e anche in Windows utilizzando Visual Studio puoi fare debugging con il debugging engine di Microsoft con codice prodotto da altri compilatori.
    In aggiunta avere a disposizione la debug information non ha nessuna relazione con l’ottimizzazione del codice.
    La differenza “Release/Debug” di VS e’ il default per i nuovi progetti (e nemmeno tanto, VS2005 non ti dava la debug info di default), che semplifica la vita e va bene per iniziare… ma sei liberissimo di andarti a cambiare le opzioni di ottimizzazione come e dove vuoi e produrre debug info anche in Release mode (e bug che si manifestano solo in Release non sono cosi’ rari).
    Insomma, il fatto che le funzioni inline non vengano compilate in tal modo nella default Debug build da Visual Studio non ha niente a che vedere con il debugging in generale.
    In Linux, dove ci sono piu’ possibilita’ per ambienti di sviluppo e quindi non esiste un “default” de facto, la cosa e’ piu’ ovvia visto che il -g a gcc (o altro compilatore) uno e’ piu’ abituato a metterlo a mano e, di nuovo, la generazione di debug information e’ indipendente dal livello di ottimizzazione.

    @Giulio: leggila come vuoi per il const, di nuovo, io vedo se e’ a sinistra o a destra dell’asterisco; se e’ a sinistra lo puoi mettere anche li’ in due posizioni come sul tipo semplice, ed il significato e’ lo stesso “l’oggetto puntato e’ costante” (non il puntatore quindi) non vedo incoerenza, mi dispiace.

    In ogni caso buon linguaggio preferito a tutti …e al prossimo articolo interessante di Cesare!

  • # 15
    homero
     scrive: 

    “gli orrori degli header files il C++ se li trascina dietro dal C ovviamente, tale padre tale figlio ”
    basta un po’ di precisione e tutto funziona alla grande….
    ottimo articolo…
    purtroppo il tempo diseguirvi si restringe sempre di piu’ faccio i complimenti a CDMAURO per la sua professionalità….

    io preferisco il C perchè lo uso da 20 anni e dei difetti ormai non me ne accorgo piu’….ma devo dire che raramente mi ha tradito…

    in realtà i FPGA mi hanno fatto venire inmente alcune idee per nuovi linguaggi modulari legati molto all’hardware ma purtroppo una vita non basta per fare tutto cio’ che si vuole….

  • # 16
    Marco
     scrive: 

    @iva
    Ben lungi dal discutere sul significato di “semantica” !!! :-D cmq con l’inline il codice della funzione è esposto al “client”, quindi perdi il significato proprio di incapsulamento (parlo del compilato, ovviamente).

    “Il discorso sul debugging con codice inline [bla bla bla] non esiste solo Microsoft […] il fatto che le funzioni inline non vengano compilate […]”

    Sono tutte ovvietà, e non ho mai detto nulla di ciò, ho solo citato VC come esempio sui generis, e come di fatto sia fondamentale conoscere come si comporta un compilatore.

  • # 17
    Bruno
     scrive: 

    @cesare
    “Se il C permettesse di derivare una struttura da un’altra (o, viceversa, estendere una struttura esistente, a seconda di come la si voglia vedere)…”

    per fare questo in c puoi utilizzare una union di struct

  • # 18
    flameman
     scrive: 

    se vi lamentate: non avvete visto ADA =P

  • # 19
    flameman
     scrive: 

    uno dei problemi che i miei clienti hanno con il C e’ il dover giustificare il casting

    prima del linguaggio C ci fu uno scisma di pensiero fra

    – Martin Richards, BCPL (Basic Combined Programming Language), “un vero programmatore sa esattamente cosa sta facendo, inutile castrare i compilatori, e se i veri programatori scrivono in assembler BCPL ragiona come ragiona l’assembler ma e’ portatile senza castrare il compilatore, non ci servono affatto linguaggi typeless”

    – Dennis Ritchie, conversione typed del linguaggio B di Ken Thompson (B era basato su BCPL), “un programmatore non sa sempre quello che sta facendo, il tempo passa, ci si dimentica, e diversi programmatori non sanno esattamente quello che ha fatto il collega, anche per questo abbiamo quantomeno bisogno di una qualche indicazione precisa, i commenti potrebbero essere non suff o addirittura ambigui, quindi fonte di entropia, bug, e quataltro: ci servono linguaggi typed, eccome!”

    in altri termini

    – typeless languages, non esistono tipi di varibile (char, int, long), ogni varibaile e’ semplicemente n-bit di memoria, o un puntatore ad essa

    – typed languages, esistono tipi di variabile (char-8bit, short-16bit, int-32bit, long-32bit), e puntatori ad esse, puntatori che, essendo le variabili di tipo diverso, ma essendo un puntatore riferito all’unita’ imndirizzabile in memoria, i puntatori dovranno rispettare anche una “aritmetica dei puntatori” (approposito di complicazioni … il 40% dei bachi si trova prprio in questa aritmetica)

    e la filosofia di fondo cambia radicalmente: se ora esistono diversi tipi, o si deve assumere che 2 variabili di diverso tipo non siano confrontabili, ne correlabili in nessuna operazione matematica, oppure bisogna fornire una “backdoor”, uno modo, uno strumento per adeguare due variabili di diverso tipo

    cosi’ nasceva
    – la backdoor del casting per i linguaggi malleabili come il C
    – il germe dell’overload per linguaggi dinamici
    – la paranoia dello “in strong type we trust” di Ada, dove al concetto di casting vanno fornite funzioni di conversione esplicitamente confezionate (definendo anche i valori di bordo dela conversione), pena il codice nemmeno si compila

    ma il bello =P del mondo avionico e’ che il codice deve essere certificabile, e per esserlo deve passare delle regole di un software chiamato QAC (sembra paradossale solo a me ?)

    il casting e’ vietato assolutamente

    quindi si pensa di esplicitare la backdoor secondo la tecnica dell’unchecked-connversion definendo una funzione assembler che traduca un tipo in un altro (in assembler perche’ QAC non controlla file in assembler …)

    le union sono ancora + vietate dei cast, cartellino rosso: quindi il problema che pone l’autore diventa non superabile

    (a meno di scrivere moduli in altri linguacci diversi dal c, moduli che si occupino di adattare e processare la giusta struttura al contesto, restituendola poi al modulo C …

    cosi’ si fa, normalmente fra ADA, c, e c++)

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

    Io mio problema è superabile con la soluzione che ho fornito, proprio perché non si ricorre al casting. A una funzione che accetta un puntatore a PyObject_HEAD puoi passare tranquillamente un puntatore a PyIntObject, perché quest’ultimo ne condivide l’intera struttura.

    @Bruno: con le union non si risolve il problema. E’ una soluzione simile all’uso delle macro.

  • # 21
    Antonio Barba (TheKaneB)
     scrive: 

    Già… l’unico utilizzo consono delle union è quando bisogna trattare dati “packed”, ad esempio vettori e matrici.
    Es:

    union
    {
    struct
    {
    unsigned char mRed, mGreen, mBlue, mAlpha;
    } Channels;
    unsigned char mChannel[4];
    unsigned int mData;

    } Pixel;

    In questo modo posso scrivere indifferentemente:

    Pixel p;
    p.mRed = 128;
    p.mChannel[0] = 128;
    p.mData = (128 & 0xff) << 24;

    preservando il valore semantico del codice.
    Poi c’è chi usa le union per fornire una sorta di polimorfismo, ad esempio:

    union
    {
    unsigned int mType;
    struct
    {
    char * mData;
    int mProva;
    } TipoSpecificoA;
    struct
    {
    float * mChupacabra;
    double * mMagnum;
    } TipoSpecificoB;
    } TipoGenerico;

    Questa forma si presta ad un utilizzo del tipo:

    #define TIPO_A 0
    #define TIPO_B 1
    TipoGenerico oggetto = getQualcosa();
    switch (oggetto.mType)
    {
    case TIPO_A: TipoA_faiQualcosa(&oggetto); break;
    case TIPO_B: TipoB_faiQualcosa(&oggetto); break;
    default: /* unsupported type */
    }

    Inutile dire che, quando ci si trova a dover implementare manualmente il polimorfismo in questo modo, probabilmente sarebbe stato meglio scegliere in prima istanza un linguaggio Object Oriented per il proprio progetto :-)

  • # 22
    Antonio Barba (TheKaneB)
     scrive: 

    nuooooo! è saltata tutta la formattazione nonostante i tag
    vabbè, anche senza indentazione si dovrebbe capire….

  • # 23
    Fabio M
     scrive: 

    Credo che lo scopo del C non sia questo.
    Il C va benissimo cosi’, e’ un linguaggio funzionale, non e’ orientato agli oggetti, ed a patto di non spingere troppo le sofisticazioni (ad esempio usando le union) ha buona portabilita’.
    (Le union sono una mina vagante, i compilatori non garantiscono mai l’impaccamento dei dati, il risultato e’ quasi sempre giornate perse a fare debug)

    E credo che qualsiasi miglioria o aggiunta sia fuorviante in quanto uno standard non e’ solo un pezzo di carta, e’ uno standard se e’ universalmente adottato.
    Gia’ cosi’ e’ un problema portare del codice fra architetture diverse e compilatori diversi, figurarsi se ci fossero degli elementi di nuovi standard.

  • # 24
    Antonio Barba (TheKaneB)
     scrive: 

    @Fabio: forse volevi dire “imperativo”… il C è tutto fuorchè funzionale :-D

  • # 25
    banryu
     scrive: 

    Forse intendeva “funzionante” :)

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

    @Antonio: se utilizzi mData in quel modo la semantica non è garantita. ;)

    Riguardo al polimorfismo concordo, ma non è questo l’oggetto del problema. Avere una gerarchia di strutture annidate non equivale al concetto di ereditarietà. Un elenco di processi e uno di file non discendono da una classe madre, ma semplicemente utilizzano una lista come struttura per organizzare le loro informazioni.

    @Fabio M: il comitato sta lavorando alla prossima versione del linguaggio, e quindi alcune innovazioni arriveranno.

    Ho fatto anche l’esempio del tipo complex: con l’ISO C99 è stato aggiunto al linguaggio, ma francamente penso che sia di poca utilità considerato l’ambito di utilizzo del C.

    Le due mancanze esposte nell’articolo, invece, fanno parte di problematiche abbastanza comuni, e sottolineavo il fatto che con esse non si stravolge il linguaggio, ma semplicemente si utilizzano meglio gli strumenti a disposizione (le struct, nello specifico). Tutto qui.

  • # 27
    Ikon
     scrive: 

    Non capisco perchè hai bisogno della derivazione e continui ad usare il C. Usa il C++. Se il C++ lo trovate limitato andate tutti a programmare in Java, nella speranza che un garbage collector cancelli un giorno l’intera JVM dalla faccia della terra :D ahuauhauh

  • # 28
    banryu
     scrive: 

    @Ikon: Grazie per aver contribuito ad elevare il contenuto informativo di questa pagina con il tuo utile e costruttivo apporto alla discussione

    Dio, che gente! :D

    @Cesare: bell’articolo, mi piacciono queste riflessioni!
    [OT]: ma WPython l’hai più ripreso in mano?
    Ci sarai all’EuroPython quest’anno?

  • # 29
    Giulio85
     scrive: 

    Nessuno ha detto che C++ sia limitato, ma è fuori discussione che la sua STL lo è, altrimenti non si userebbe Boost o Qt.

    Siamo anche nel 2011 qualcuno dovrebbe ricordarlo.

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

    @Ikon: non stiamo parlando di programmazione a oggetti, se non ti fosse ancora chiaro.

    @banryu: WPython non lo porto più avanti. Se non c’è interesse da parte della comunità, viene meno lo stimolo di migliorarlo (e ce ne sarebbero di cose da ottimizzare, te l’assicuro).

    Sì, anche quest’anno sarò a Firenze, con un talk bilingue: http://ep2011.europython.eu/p3/schedule/ep2011/ Speriamo bene (per l’inglese :P).

  • # 31
    flameman
     scrive: 

    @Cesare
    no, non in contesto avionico!

    o mi definisci un tipo puntatore che sia LO stesso che usi per definire i puntatori a PyObject_HEAD e PyIntObject

    e usi questa definizione in ogni chiamata a funzione, e ogni volta che ti riferisci a quei 2 oggetti

    oppure una funzione che accetta un puntatore a PyObject_HEAD NON puoi passare tranquillamente un puntatore a PyIntObject, perché quest’ultimo ne condivide l’intera struttura.

    Purtroppo no ! Proprio questo e’ il punto: QAC vede due “cose” distinte, e ti OBBLIGA a creare una function unchecked-conversion che trasformi un puntatore a PyObject_HEAD in un puntatore a PyIntObject

    e’ paradossale, me ne rendo conto: condividono la stessa struttura, ma questo QAC non lo sa.

    Sei anche obbligato (ma alla fine e’ la sola scelta giusta) a definire dei tipi puntatore

    typdef … per capirci.

    In questo caso la function di unchecked conversion non fa altro che fare prendersi in ingresso, come parametro, un puntatore ad un tipoA, interpretarelo come puro indirizzo di memoria (quale un puntatore e’), e retituirlo come puntatore di ritorno.

    se la vai ad implementare per powerpc, non fai altro che scrivere una inline di return (perche’ solitamente ill primo parametro in ingresso corrisponde al return della function … vale per i PPC E MIPS, solitamente … dipende dal compilaotore)

    in assembler m68k
    faresti un move.l #xxparam_IN0(%SP),0(%SP); rts

    assurdo ? Si, ma ringrazio la pedanteria avionica
    xke’ forzando approcci come questo
    nel complesso fa scrivere codice con molte meno porcate

    (del resto per non suicidarsi con ADA
    bisogna davvero imparare a fare meno porcate)

    hobbisticamente parlando xro’
    continuo a pensarla come Martin Richards

    no, sono un bugiardo
    predico bene e ….
    … sto provando ADA su 68hc11 e AVR (micro ad 8bit)

    =P

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

    Ma infatti il problema era il mio, non di QAC. ;)

  • # 33
    ufo.rob
     scrive: 

    Bisogna fare il casting a PyObject ma non hai scritto quest’ultimo tipo com’è definito, o mi sono perso qualcosa?

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

    E’ sostanzialmente equivalente a PyObject_HEAD che ho definito prima.

  • # 35
    ufo.rob
     scrive: 

    Quindi avevo capito bene e da qualche parte c’è una dichiarazione ESPLICITA così oltre alla macro PyObject_HEAD?

    typedef struct {
    Py_ssize_t ob_refcnt;
    struct _typeobject *ob_type;
    } PyObject;

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

    La trovi in Include/object.h:

    typedef struct _object {
    _PyObject_HEAD_EXTRA
    Py_ssize_t ob_refcnt;
    struct _typeobject *ob_type;
    } PyObject;

  • # 37
    Ufo.rob
     scrive: 

    “I pascaliani avrebbero fatto ricorso al costrutto with”. Non a caso in C# il cui sviluppo è stato guidato Anders Hejlsberg c’è l’Object Initialization che funziona in modo simile permettendo di non riportare continuamente il nome dell’istanza http://msdn.microsoft.com/en-us/library/vstudio/bb397680.aspx anche se in effetti non conosco un modo simile per ACCEDERE agli attributi senza indicare esplicitamente tutte le volte il nome dell’istanza

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

    L’Object Initialization è una gran comodità, che ho usato come il pane quando ho lavorato a un progetto per Windows Phone. Purtroppo non ci altri “zuccheri sintattici”, oltre questo.

    Debbo dire, per correttezza, che il costrutto with è fonte di gioie, ma anche di critiche. Persino da pascaliani. A mio avviso rimane uno strumento che, come per tutte le cose, va usato saggiamente.

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.