View Full Version : Microsoft: identificate 22 nuove vulnerabilità
repne scasb
18-10-2004, 20:00
repne scasb
18-10-2004, 20:02
Originariamente inviato da cdimauro
Qui lo sai bene che entrano in gioco dei notevoli interessi: nel campo delle schede video e delle GPU c'è una guerra con Ati, e nessuno può permettersi il lusso di giocare a carte scoperte.
Io penso che il più delle volte questi "notevoli interessi" sia una scusa per nascondere le porcate che si son fatte.. da come ho potuto vedere più volte in giro per ditte (daccordo, PMI, ma è la mia esperienza diretta. Per il resto mi fido della signature di ilsensine). Cmq, riguardo al nostro discorso, il dual-head delle ati funziona benissimo con driver OPEN SOURCE incluso in Xfree 4.4, come puoi vedere qui (http://www.xfree86.org/current/radeon.4.html). Davvero pensi che Nvidia andrebbe in fallimento a rivelare un pò di specifiche? Ma lasciamo stare le GPU, parliamo di chipset.. comè che il primo Nforce non han voluto dire NULLA? che poi è saltato fuori che il controller IDE funziona perfettamente col driver di un amd, e la scheda di rete.. un ragazzo ha fatto reverse engeneering e l'ha fatta funzionare lo stesso... ma davvero vuoi farmi credere che ci sono incredibili interessi dietro alle specifiche di una SCHEDA DI RETE????? Mi sento preso in giro se mi dicono così.
DioBrando
18-10-2004, 23:00
Originariamente inviato da RaouL_BennetH
Credo, dopo tanti post, e io invece li ho seguiti e letti tutti tutti :D che nemmeno fek e cdmauro intendano che sia uno sprovveduto, ma solo che forse, e sottolineo il forse, non è un programmatore di c++, ne risulterebbe che non "pensa" in c++ e, di conseguenza, non ha e non lavora al kernel in c++.
+ che non sia un gran programmatore in C++, si sn soffermati sul fatto che le sue ( di Torvalds) convinzioni ( erronee?) hanno frenato lo sviluppo di linux che a quest'ora avrebbe potuto dotarsi degli strumenti in + forniti da questo linguaggio e dalla programmazione a oggetti in generale, rispetto al "vetusto" seppur valido C.
DioBrando
18-10-2004, 23:12
Originariamente inviato da fek
Sto leggendo con molto interesse:
Magari mettere l codice di cleanup in una funzione e richiamando quella, in una bella clausola __finally sarebbe un Engineering principle ancora migliore?
Non e' teoria: e' dimostrato scientificamente che il numero di difetti in un codice e' proporzionale al numero di goto.
questa la sò questa la sò! :D
E' stato dimostrato matematicamente ( + che scientificamente...insomma siamo + precisi :D) che lo sviluppo logico
- sequenza
- selezione
- salto
è emulabile in qls delle rappresentazioni possibili con lo sviluppo
- sequenza
- selezione
- iterazione
In parole povere Bohm e Jacopini nel 1968 teorizzano e dimostrano che qls algoritmo scritto nella forma 1 può essere riprodotto esattamente nella forma 2.
Ne segue la nascita di linguaggi che si avvalgono di questa "scoperta" come il C, Pascal, Basic ecc.
Non solo, proprio perchè è provato che il salto diventa in un programma complesso motivo di "perdita di tempo" ( e quindi di soldi) per il programmatore che si troverà ad affinare codice con questo tipo di istruzioni, nei linguaggi + recenti come il Java il "goto" ( come il "const") è formalmente "bandito".
Lo si può scrivere ma poi il compilatore restituisce un errore.
Originariamente inviato da repne scasb
Ancora, non presuppongo che la stima possa essere fatta solo a posteriori, e' possibile farla anche senza l'uso di un compilatore. Il problema e': C'e' qualcuno in grado di farla? Se si, parliamone. Mi sembra (potrei sbagliarmi) che ti sei convinto che "escluda" o ritenga "impossibile" giudicare le prestazione assolute di un linguaggio di programmazione esclusivamente mediane l'analisi del linguaggio stesso. Assolutamente no, lo ritengo plausibile e anche possibile.
Diciamo che avevo interpretato il tuo punto di vista in questo modo: "è impossibile o comunque molto difficile fino a prova contraria (e non appena questa prova arriva, se utile/necessario, cambiamo metodologia di analisi), pertanto, basandoci solo sullo stato dell'arte, preferisco fare un'analisi del linguaggio connesso al compilatore, poichè in ogni caso il codice prodotto potrebbe sovvertire i risultati delle stime teoriche." Mi rendo conto che il tuo punto di vista è in parte simile a questo, ma più possibilista. Io (te lo ripeterò fino alla nausea, dal basso della mia ignoranza tecnica specifica sull'argomento (per questioni che richiedano conoscenze tecniche molto approfondite)) ho fatto un ragionamento diverso: supponiamo che esista, o che si possa formulare, una metodologia non connessa e cerchiamo di capire i vantaggi di una siffatta analisi. Vantaggi, a mio parere, potrebbero consistere nel giudicare meglio anche casi particolari, poichè permetterebbe di capire se il problema è relativo ad un particolare compilatore (ed eventualmente sostituibile con un altro "migliore" in QUEL contesto, se esiste) oppure se il problema prestazionale risiede nella difficoltà intrinseca di tradurre i costrutti del C++ in codice almeno altrettanto efficiente di quello prodotto compilando in C, per cui la scelta del C è obbligata. Ancora, mi chiedevo se fosse possibile (o come mai non fosse possibile) realizzare un compilatore C++ in grado di ottimizzare codice che presenti esclusivamente costrutti propri del C con la stessa efficienza di un compilatore C, a prescindere dai "limiti"/esigenze/scelte architetturali/obiettivi specifici di un particolare compilatore. Ammetto di aver fatto forse un po' di confusione nel trattare in maniera equivalente casi particolari e casi generali.
Se vuoi possiamo spostare la discussione sulla valutazione delle prestazioni assolute di un linguaggio di programmazione in base alle strutture stesse del linguaggio ma: mi devo prima documentare, sullo "stato dell'arte" in questo campo (non posso sapere tutto di tutto). Se t'interessa avvertimi.
Chiaramente sarebbe interessante. Per me l'informatica (in tutti i suoi "rami") è non solo una passione, ma anche una (futura :D ma spero non troppo) fonte di lavoro, per cui DEVO documentarmi più che posso. Del resto, come avevo detto in altra sede , uno dei miei "obbiettivi" su questo forum è quello di "spolpare" il più possibile gente che ne sa più di me... :D ;). Poi mi piace cercare di riflettere, usare la testa e confrontare le mie idee (o sparate, sono entrambi punti di vista validi :D) anche su cose che conosco poco (anche molto poco): credo che un buon Ingegnere Informatico (come un buon "qualsiasi cosa") debba saper usare la testa prima di saper programmare/progettare/fare qualcosa che già sa fare. Ti avverto comunque che se ci addentriamo in aspetti molto tecnici potrei non riuscire a darti (almeno io) un feedback significativo (o quanto meno interessante). Posso (continuare a) fare ipotesi, supposizioni, cercare di interpretare ragionamenti e conclusioni altrui, per proporre spunti di riflessione (e chiarirmi meglio le idee :D poi, oh, mica sono "tabula rasa", qualcosa la so fare :D).
No, nel caso specifico. Non so in generale (NOTA: Non ho detto "No, in generale"). Non escludo che ci possano essere casi in cui possa rispondere "Si".
Se pretendessi di avere risposte necessarie e universali su argomenti così particolari e legati al contesto farei meglio a cambiare progetti per il futuro (lavorativo) :D
Curiosamente io mi occupo proprio di questo (a tempo perso). Trasformo in assembly le parti di codice "critico" in una 20-ina tra microprocessori e microcontrollori.
Sai che non l'avrei mai detto? Da chi per mestiere progetta compilatori e ottimizza codice mi sarei aspettato tutto, ma non questo! :O :D
Ancora: con tutto il rispetto per te e per tutti gli altri sviluppatori/utilizzatori di quel compilatore, un test esaustivo presupporrebbe un confronto tra più compilatori :O(qui mi aspetto di ricevere un bel "vaffa"...:D)
...
"Io non voglio dimostrare che in "GENERALE" il C ha prestazione assolute migliori del C++."
"Io voglio dimostrare che in casi "PARTICOLARI" il C ha prestazione assolute migliori del C++".
Innanzi tutto, quella "critica" ai tuoi test voleva essere una via di mezzo tra una battuta e una provocazione scherzosa, sottintendendo questo:
Sono perfettamente d'accordo sulla necessità di contestualizzare il discorso e di discutere su casi particolari, però mi piacerebbe comunque poter distinguere quanto le prestazioni siano legate alla natura del C e quanto invece all'architettura interna di un compilatore specifico, per poter fare un discorso un po' più generale, fermo restando l'impossibilità di fare un discorso troppo generale. Credo che si possa tentare di allargare la portata dei nostri ragionamenti supponendo di avere un "buon" compilatore "general purpose", intendendo per general purpose l'essere stato progettato per poter ottimizzare bene sia i costrutti propri del C, sia quelli del C++, in modo da poter programmare in C++, sfruttandone tutti i vantaggi (soprattutto per le prestazioni complessive), e usare prevalentemente il C (ed eventualmente l'assembly) in determinate sezioni per ottimizzarle al meglio, se possibile/necessario.
E qui passiamo al discorso di Linux, perchè è vero che si tratta (o potrebbe trattarsi) di un caso particolare (o particolarissimo), ma a mio avviso si proietta in un contesto più generale, essendo scritto in C ma con una forte propensione all'uso di strutture molto vicine a quelle proprie del C++. Ora io mi chiedo: se le prestazioni assolute sono superiori anche in questo contesto, ciò è dovuto ad una superiorità intrinseca al C rispetto al C++, oppure accade che, a prescindere dalla realizzabilità di un compilatore C++ general purpose, esiste (può esistere) un qualche compilatore C che ottimizza costrutti C++ nativi-like meglio di qualsiasi (alcuni) compilatore C++ nativo? Ma, se la risposta ad una qualunque di queste domande fosse affermativa, questo paradossalmente non diventerebbe un punto a favore del C++? Mi spiego: trovato un siffatto compilatore si potrebbe farne (tentarne) il porting su altre piattaforme (e avremmo eliminato il vincolo del compilatore nella non generalizzabilità dei casi particolari), quindi si potrebbe studiare il modo migliore per tradurre il codice (sorgente) C++ in codice (sorgente) C e costruire un preprocessore C++ per il compilatore C (e così eliminiamo anche il vincolo legato alla natura del linguaggio), compiendo una sorta di ritorno al passato ma consapevole, mirata e ben ponderata, ed ecco che avremmo realizzato il compilatore C++ "general purpose" perfetto, in grado di fondere i pregi di entrambi i linguaggi. IMHO, ovviamente.
Stai dando per scondato che il voglia dimostrare che in "GENRALE" le prestazioni assolute del C siamo maggiori delle prestazioni assolute del C++. Ancora: non voglio dimostrarlo in "GENERALE" ma in "PARTICOLARE" (se non sono chiara dimmelo).
Chiarissima. Spero di esserlo stato io altrettanto nell'esporre il mio pensiero :)
L'esempio di cdimauro, purtroppo, e' in quel caso particolare (VLIW, assegnazione a singola variabili dopo salto condizionato (per capirci una CMOV), tale che il programmatore "erra" sia se usa un "IF" sia se usa un "?". Non deve utilizzare ne l'uno ne l'atro, in quanto il compilatore e' "ottimizzato" per trattare il codice corretto che ho riportato nel mio susseguente messaggio (non c'e' ne IF ne ?).
Al di là della specificità del caso "LX", sono d'accordo sull'algoritmo per il calcolo del massimo. Però (evidentemente mi sono espresso male) volevo fare un'osservazione più generale: personalmente ritengo che non si possano eliminare TUTTI i salti condizionati in un programma (avrebbe, credo, conseguenze nefaste sulle prestazioni complessive), per cui, assumendo di non voler rinunciare a questo strumento e di effettuare una scelta tra if-else e ?:, se il codice non risultasse ugualmente efficiente (non dico che dovrebbe essere massimamente efficiente con entrambe o una delle due forme, faccio solo un confronto tra le due), dove sbaglia il programmatore nella scelta "fra le due"? Ok che il problema era limitato al calcolo di min e max, ma si tratta pur sempre di un problema del compilatore, e se si verificasse in altre circostanze (altri costrutti equivalenti, altri compilatori, altri linguaggi), non dimostrerebbe che il compilatore necessita di una messa a punto (parziale), discolpando il programmatore?
Ancora su quell'algoritmo: hai detto che la versione che hai scritto è poco ottimizzata. Francamente io non mi sono mai cimentato molto in ottimizzazioni di questo tipo, così mirate, ma mi sono fatto più o meno un'idea su come si potrebbe ottimizzare. Innanzi tutto, l'AND bitwise tra (b-a) e SIGN_BIT mi sembra del tutto inutile, in quanto i bit posti a zero vengono eliminati dallo shift successivo, se non per il fatto che (se non ricordo male i meccanismi di casting automatico nelle operazioni) l'intero b-a viene trasformato in un unsigned, cosa che garantisce l'esecuzione dello shift a destra successivo come uno shift logico, per cui, a seconda dei casi, avrò 1*(a-b) o 0*(a-b). Ma a questo punto, se mi serve un casting (che comunque faccio nella valutazione di SIGN_BIT) non è meglio farlo direttamente su (b-a)?
Quindi sostituirei il calcolo del massimo con
max = (((unsigned)(b-a))>>SIGN_POS)*(a-b) + b;
ed elimino la macro SIGN_BIT. Basta questo o si può fare qualcos'altro?
Ciao:)
repne scasb
19-10-2004, 09:19
afterburner
19-10-2004, 09:47
Originariamente inviato da repne scasb
256.
You'll never find it :D :D :D
Originariamente inviato da repne scasb
Ho capito che piega desideresti prendesse la discussione, vorrei evitare di impelagarmici.
Sommariamente, pensi che fare un compilatore che ottimizza il codice come se fosse C finchè non trova costrutti (parole chiavi e quant'altro serva) propri del C++, e a questo punto passa alle ottimizzazioni specifiche, presenti delle difficoltà implementative, anche partendo da un (buon) compilatore C preesistente?
Potrebbe essere una strategia di progettazione valida?
Oddio, mi sono persa. Dovresti spiegarti meglio. Scusa.
Ok, cerco di raggiungerti con una torcia (sempre che io sia in grado di trovare la strada giusta :D). Le scuse sono superflue, non esageriamo ;)... di conseguenza non le accetto :D
Ero partito (implicitamente) da un'osservazione di cdimauro:
"un compilatore C++ che converte il sorgente processato in un altro sorgente, ma stavolta in C e che lo dà poi in pasto a un compilatore C, per forza di cose non può che generare lo stesso codice se non viene utilizzata alcuna caratteristica propria del C++. A meno che non introduca intenzionalmente del codice in più."
Questo dimostra che, anche assumendo che non è possibile creare un compilatore C++ nativo che sia (sempre) più efficiente di un compilatore C, è comunque sempre possibile scrivere un compilatore C++ (ho tolto volutamente il termine "nativo") che sia almeno efficiente quanto il miglior compilatore C, ovvero come ogni compilatore C: basta che il compilatore C++ traduca il codice in C e un qualsiasi compilatore C (nel caso ideale, il migliore) lo compili.
Passiamo a Linux. Assumiamo che si tratti di un "caso particolare" in cui la coppia C/compilatore da prestazioni superiori alle coppie C++/compilatore eventualmente testate.
Ciò è dovuto alle "prestazioni assolute del compilatore specifico"? Allora si potrebbe provare un compilatore C++ diverso, oppure, se non ci si fida dei compilatori C++ si potrebbe costruire un preprocessore che traduca il C++ in C, in modo da avere le prestazioni assolute del C su quel compilatore unitamente alla possibilità di sfruttare, quando necessario, le potenzialità del C++ nella creazione/manipolazione di oggetti e nell'ottimizzazione delle prestazioni complessive.
Ciò è dovuto invece alla superiorità delle "prestazioni assolute del linguaggio"? Potemmo distinguere due casi: questo è vero in generale, ovvero in particolare, a causa dei costrutti e algoritmi implementati. Assumiamo che sia vero in particolare: i costrutti utilizzati servono a costruire e gestire oggetti, cosa che fa anche il C++ in maniera nativa e più semplice da sfruttare per il programmatore (perchè non deve curare personalmente molti dettagli), ma allora una traduzione del C++ in C ne consentirebbe un utilizzo altrettanto efficiente. Assumiamo invece che la superiorità del linguaggio valga in generale quando si usino i costrutti del C: questo esempio dimostra che in un caso particolare questo è vero anche quando si usino detti costrutti per implementare strutture dati e scelte architetturali tipiche del C++ (o quanto meno molto vicine), ma poichè le strutture proprie del C++, in quanto derivate dal C, sono sempre traducibili in C, diventa sempre possibile ottenere in C++ almeno le stesse prestazioni assolute del C, per cui ciò è vero anche in questo caso particolare.
Ciò è dovuto in parte alle caratteristiche del linguaggio e in parte a quelle del compilatore, ma non è possibile valutare in che misura incidano le une e le altre? In tal caso le considerazioni precedenti restano valide; inoltre permettono di dividere il problema della ricerca di prestazioni assolute massime in C++ pari a quelle ottenibili in C in due parti:
a) la ricerca di ottimizzazione delle prestazioni massime del compilatore, che si risolve nella ricerca del miglior compilatore C disponibile (eventualmente in relazione al problema, se fosse possibile stabilirlo);
b) la ricerca di ottimizzazione delle prestazioni assolute del linguaggio, che si risolve nella ricerca del modo migliore per tradurre i costrutti del C++ in C.
Di conseguenza, se la logica dei miei ragionamenti non fallisce, anche in questo caso particolare è possibile usare il C++ con la stessa efficienza del C, previa traduzione del C++ in C. Tuttavia il risultato e il metodo non sono generalizzabili, poichè:
1) resta da verificare quanto assunto per ipotesi, cioè che in questo caso si possano ottenere in C risultati migliori, in termini di prestazioni massime assolute, di quelle ottenibili lavorando nativamente in C++ (con il linguaggio e un compilatore nativo), possiamo solo supporre che ciò sia possibile in mancanza di altri elementi;
2) non si dimostra che i compilatori nativi C siano superiori a quelli nativi C++ né in generale, né in particolare, tutt'al più si può affermare che ciò sia o possa essere vero nel confronto tra specifici compilatori, ma non necessariamente per particolari problemi;
3) non si dimostra neanche che un compilatore C++ nativo possa produrre codice per la gestione di oggetti o per costrutti propri del C in modo altrettanto o più efficiente di un compilatore nativo C (in realtà non si prende neanche in esame il problema), si dimostra o si prova ulteriormente solo che sia in generale, sia in particolare, è comunque possibile costruire un compilatore C++ che produca codice altrettanto efficiente del C;
4) non sono misurabili le differenze tra le prestazioni assolute legate ai linguaggi, ma si può indirettamente affermare che, essendo possibile usare in C++ sia i costrutti nativi, sia quelli del C, ed essendo possibile annullare le differenze legate al compilatore, non vi è sostanzialmente vantaggio per il C (in termini di prestazioni massime assolute del linguaggio).
Bene. Hai colto, la prima ottimizzazione. Quella che hai scritto e' la funzione MAX per achitetture VLIW (l'AND l'ho aggiunto io per disottimizzarla ed e come hai notato ridondante). Ti rimane da togliere la moltiplicazione e sei quasi arrivato alla funzione MAX per CPU x86.
Mi hai messo la pulce nell'orecchio, ci devo pensare.
La prima cosa che mi viene in mente è fare lo shift aritmetico a destra (eliminando il casting) e trasformare il prodotto in un and bit a bit, che dovrebbe essere più veloce, ma non ho la certezza che lo shift a destra su interi con segno venga implementato in C, su macchine x86, come aritmetico (so che per lo standard è sempre di tipo logico sugli unsigned, ma per i signed dipende dalla macchina e - forse - dall'implementazione). Per il resto, ci devo pensare. A proposito, sui VLIW com'è implementato (e utilizzabile in C)? E le operazioni bitwise sono svolte dall'alu o da qualche algoritmo simil-MAC (su operandi float)? Togliere la moltiplicazione li non servirebbe, perchè è eseguita in sieme alla somma successiva in un unico MAC, pero le differenze a-b, b-a e (sizeof(a)<<3)-1 non comportano un MAC ciascuna? In tal caso, avevo pensato ad un'altra modifica, osservando che lo shift aritmetico produrrebbe sempre 0 o -1 in complemento a 2 indipendentemente dalla sua "entità", per cui, nota la dimensione dei registri interi sulla macchina, si potrebbe eliminare la macro SIGN_POS e sostituirla con uno >> pari alla dimensione dei registri interi, effettuando quindi un and bitwise con 1 per ottenere ancora 0 o 1. Ovviamente avrebbe senso solo se lo shift a destra tra signed è aritmetico, e l'operazione derivante è più veloce della differenza (MAC) e della routine relativa all'operatore sizeof. Se questo è vero, allora si potrebbe anche osservare che la stessa operazione su a-b produce gli stessi risultati, ma sugli "input" invertiti, per cui aggiungendo uno xor bitwise si otterrebbe ancora 0 o 1 in modo corretto: questo consentirebbe di eliminare il MAC su b-a eseguendo una volta sola a-b (eventualmente, non volendosi affidare all'ottimizzazione del compilatore, si potrebbe precalcolare e memorizzare in una variabile register), per cui si avrebbe:
//regiser int delta;
...
//delta = a - b;
max = ((((a-b)>>REG_DIM))&1)^1)*(a-b) + b;
//oppure
//max = ((delta >> REG_DIM) & 1)^1)*delta + b;
Ci ho azzeccato o l'ho sparata?
Ciao :)
repne scasb
20-10-2004, 10:51
Di conseguenza, se la logica dei miei ragionamenti non fallisce, anche in questo caso particolare è possibile usare il C++ con la stessa efficienza del C, previa traduzione del C++ in C. Tuttavia il risultato e il metodo non sono generalizzabili, poichè:
Sono sostanzialmente d'accordo con te. Voglio solo aggiungere una considerazione: i compilatori C++ nativi sono in genere piu' efficienti perche' possono fare assunzioni sulla natura dei costrutti e conoscono i concetti dietro a quei costrutti stessi, rispetto ad un traduttore che traduca il codice in C, che poi si occupa della traduzione in linguaggio macchina.
Saltare un passaggio permette di limare qualcosa in piu' sulle prestazioni, ma il tuo discorso e' a mio avviso sostanzialmente corretto.
Scusate se mi intrometto. Vorrei, se mi è concesso, dare il mio piccolo contributo alla discussione su alcuni aspetti. Mi riferisco in particolare a cose scritte da fek, xeal, ilsensine. Forse sto per scrivere un mucchio di ovvietà ma mi sembrano sfumature attinenti al vostro dicorso, che vi sono sfuggite e che invece mi sembrano rilevanti
Sulla "diatriba" delle performance del C vs. il C++ in generale, a mio avviso state prendendo l'argomento per un verso che non è esattamente quello più adatto perchè non tiene nel dovuto conto (mi sembra) della principale differenza tra i due linguaggi, cioè il paradigma di programmazione e, soprattutto, i conseguenti "effetti collaterali" dovuti a questa differenza. Non è una questione di compilatore C o compilatore C++ più o meno nativi, e nemmeno una questione di linguaggio C (i suoi costrutti) che sia intrinsecamente più o meno veloce del linguaggio C++. E' invece una questione di metodo di programmazione. Programmare in C per un programmatore significa farsi carico personalmente della gestione di tutta una serie di problematiche che invece in C++ vengono "nascoste" poichè vengono risolte dal compilatore; di contro significa anche che il programmatore in C ha un maggiore controllo sul codice che verrà generato e di conseguenza sull'esatta entità e sequenza delle istruzioni che la macchina eseguirà. A causa della differenza del paradigma di programmazione il C++ inserisce rispetto al C tutta una serie di astrazioni per il programmatore paragonabili (in analogia, non in similitudine) alle astrazioni che il C inserisce rispetto all'Assembly e perciò le eventuali differenze di prestazioni che ci possono essere tra il codice generato a partire da un sorgente C da uno equivalente nelle funzioni ma generato a partire da un sorgente C++ (che sfrutti la OOP, ovviamente) sono essenzialmente dovute al "potere di controllo" che il programmatore ha sul codice generato. Nel C questo potere è maggiore che nel C++, di conseguenza un bravissimo programmatore C è POTENZIALMENTE in grado di ottenere SEMPRE un programma più performante rispetto a quello sviluppato da un altrettanto bravissimo programmatore C++. Per esempio è indubbio che in C si possono "emulare" tutte le funzionalità introdotte dal C++ e l'eventuale implementazione di queste funzionalità sarebbe totalmente nelle mani del programmatore anzichè del compilatore. Oltre a queste questioni di maggior "potere di controllo" sul codice generato (e quindi possibilità di scrivere codice ottimizzato per una certa situazione o contesto), il paradigma OOP introduce intrinsecamente per sua stessa natura altre fonti di potenziale rallentamento. Esempio: se da una parte l'utilizzo dell'ereditarietà può forse dare vantaggi prestazionali al C++ rispetto alla sua "emulazione" in C, dall'altra l'utilizzo dell'incapsulamento inserisce invece un "percorso forzato" ai dati in memoria che un programmatore C++ si trova a DOVER seguire, laddove invece il programmatore C può decidere di "prendere una scorciatoia" scrivendo/leggendo direttamente i dati di una struttura, senza passare per le famose getter/setter functions. Questo tanto per far l'esempio più semplice ed eclatante di un "percorso forzato" presente in C++ e che può causare (specie se la funzione getter/setter in questione non viene inline-izzata) prestazioni peggiori rispetto all'"accesso diretto"; un pò come la differenza che c'è nell'accedere alle periferiche tramite le porte I/O del processore invece che tramite DMA
Detto così si potrebbe dire che la programmazione in C++ non abbia scampo nel confronto prestazionale con la programmazione in C. Per "fortuna" del C++ le cose non stanno esattamente così perchè nel momento in cui si passa dalla teoria alla pratica molte cose, al solito, cambiano: un programmatore C, anche il migliore del mondo, che conosca a menadito sia il processore sia il compilatore che utilizza e sarebbe quindi perfettamente in grado di scrivere programmi C di valore prestazionale assoluto, nella maggior parte dei casi preferisce (giustamente) sacrificare un pò di prestazioni a favore di leggibilità, manutenibilità, estensibilità del programma; il più delle volte questo "modus-programmandi" in C finisce per portare il programmatore a scrivere codice sorgente che all'atto pratico "emula" quegli stessi meccanismi che un linguaggio OOP implementa nativamente, ed ecco che quindi il potenziale "vantaggio" iniziale del C rispetto al C++ tende a ridursi se non addirittura annullarsi (a seconda della bontà del programmatore nonchè ovviamente della bontà dei compilatori C e C++ usati). Ergo, a parità di bontà di programmatori e compilatori, le prestazioni di un linguaggio procedurale in genere rispetto ad uno OO direi che dipendono da se e come si sfruttano i rispettivi stili di programmazione
Per quanto riguarda lo specifico di usare il C++ per lo sviluppo di un kernel, il problema invece non è una questione di paradigma di programmazione dato che ad es. il kernel Linux ha chiaramente un approccio OO pur essendo scritto in C. In un kernel il fattore "potere di controllo" da parte del programmatore diventa cruciale, a differenza di un qualsiasi software in userspace, e quindi il C avrà sempre un vantaggio sul C++; alcune cose che in C sono ordinaria amministrazione, espressamente consentite e non di rado incoraggiate (e su cui gli odierni programmatori di kernel si appoggiano in maniera pesante per sveltire lo sviluppo) sono vere e proprie bestemmie in C++. Un paio di esempi: la tipizzazione a dir poco lasciva (Dio solo sa quante void* ci sono nel kernel di Linux) o la disinvoltura con cui un modulo del programma si "intromette" negli affari di un altro modulo andandone a modificare i dati. Sicuramente in C++ si potrebbe risolvere adeguatamente ed in maniera elegante tutte quelle problematiche, rendendo tra l'altro il codice sorgente più pulito, più lineare, più estensibile, ecc. ma ciò richiederebbe uno sforzo di re-designing prima ancora che di re-factoring. Molte cose che in C le puoi semplicemente fare, in C++ bisogna che il programmatore originario abbia PENSATO e previsto che un altro programmatore potrebbe avere necessità di farle e gli abbia quindi lasciato "la porta aperta" o al massimo socchiusa. Riprendendo un esempio che ilsensine aveva fatto a fek, quello del puntatore a funzione per la ioctl delle file_operations del kernel Linux che in C basta andare a sostituire se si vuole, la soluzione del pattern Visitor proposta da fek non è proprio equivalente perchè ciò che si fa realmente in C equivarrebbe in C++ al modificare la vtable di una classe, cosa che in C++ è ovviamente impossibile. La soluzione Visitor è solo un "workaround" in perfetto stile OOP a cui il C++ ti "costringe" e che rallenta l'esecuzione ma che soprattutto deve essere stata espressamente prevista da chi avesse scritto la classe "file" contenente il metodo "ioctl" a cui un altro programmatore volesse aggiungere un Visitor. Per generalizzare il concetto, in un kernel scritto in C un programmatore può alterare a posteriori ed anche al volo (inserendo e togliendo moduli del kernel) il comportamento di un'altra parte del kernel, senza che il programmatore di quell'altra parte abbia previsto espressamente di consentirlo. In C++ questo non è possibile e se questa "imposizione" del C++ è generalmente auspicabile e induce certamente ad un Buon Design, può risultare un ostacolo per un kernel che voglia essere "metamorfico" come è Linux. Ovviamente anche in C ci sono limiti ben precisi ma sono molto più permissivi di quelli del C++ ed inoltre è più rapido e semplice aggirarli o toglierli all'occorrenza
Comunque penso che avendo un ottimo compilatore C++ NATIVO (cioè che scriva direttamente codice macchina) si potrebbe scrivere un kernel dalle prestazioni più che ottime o persino equivalenti a quelli di oggi scritti in "C con approccio OO", ma avrebbe quelle rigidità intrinseche al C++ (essendo un linguaggio OOP) che potrebbero non favorire uno sviluppo modello "bazar" quale è quello di Linux, mentre sarebbe l'optimum per lo sviluppo modello "cattedrale" quali ad es. QNX o anche Windows. Questo lo dico riflettendo su una mia personale esperienza che non so se capiti anche a voi: quando devo CAPIRE un software scritto da altri ci riesco meglio se è scritto in C mentre quando devo SCRIVERE un software mi trovo meglio (molto meglio) in C++
Originariamente inviato da repne scasb
Dipende dal VLIW, in generale puoi fare (a-b) senza MAC (si puo' fare anche con EPIC),
Ho capito. Credevo che il MAC venisse usato quasi sempre per le operazioni su interi (e preferito dai compilatori), anche per le somme, per cui ho cercato di eliminarne qualcuno...
inoltre puo' caricare il MAC con l'ADD negato,
Quindi il compilatore, nel suddividere quella espressione in token, oltre a ricercare eventuali calcoli ripetuti (cosa che supponevo/speravo, ma senza averne certezza: di qui la mia ipotesi su un eventuale uso di "register int delta"), confronta anche le espressioni contenute nei token per trovare "somiglianze" che consentano di ridurre l'una ad una operazione più semplice da eseguire sul risultato dell'altra (ovviamente, dove non si debbano fare miracoli)? Non lo sapevo...
inoltre ((sizeof(a)<<3)-1) e' una costante
Credevo che SIGN_POS fosse una macro che il compilatore sostituisce letteralmente con le espressioni contenute nella sua definizione, da valutare eventualmente a tempo di esecuzione. In effetti però è definita come una costante, non come una funzione da elaborare. Ne deduco che è possibile definire costanti mediante espressioni valutate dal compilatore e trasformate nel valore numerico corrispondente. Confesso che non ne avevo idea...
Per gli shift non ci sono problemi.
Quindi, togliendo il casting in unsigned ho la certezza di ottenere uno shift aritmetico (su VLIW e x86)? Buono a sapersi. Ci sono macchine che implementano solo shift logici? Oppure ti riferivi al fatto che non sono effettuati dall'FPU ma dall'ALU?
Per CPU_CISC in generale (salvo casi particolari):
1) Puoi eliminare l'AND(1), nel tuo esempio:
Infatti, quell'esempio lo avevo pensato per architetture VLIW/RISC-like che non consentissero di effettuare somme tra interi se non con l'FPU (ma effettuassero rapidamente le operazioni logiche, con unità apposite), per eliminare qualche MAC di troppo.
Su di un CISC, non mi preoccuperei tanto di una somma algebrica, poichè, per quanto ne so, viene effettuata con anticipo del riporto, per cui non dovrebbero esserci latenze particolari rispetto alle operazioni logiche, se non quelle legate al maggior numero di porte (che poi Dio solo sa con quali trucchetti vengono comunque ridotte a una "manciata" di transistor (o poche "manciate", in genere molti meno transistor di quanto si otterrebbe a progettando i circuiti con porte logiche)).
Chiaramente c'e' sempre la soluzione per CISC senza moltiplicazione. Ti arrendi?
Ne deduco che la mia idea di fare uno shift aritmetico, in modo sostituire la moltiplicazione con un and bitwise non va bene. In realtà non ci ho più pensato molto, comunque, ok, mi arrendo... :)
Sono sostanzialmente d'accordo con te. Voglio solo aggiungere una considerazione: i compilatori C++ nativi sono in genere piu' efficienti perche' possono fare assunzioni sulla natura dei costrutti e conoscono i concetti dietro a quei costrutti stessi, rispetto ad un traduttore che traduca il codice in C, che poi si occupa della traduzione in linguaggio macchina.
Lo penso anch'io, in generale. Solo che andrebbe verificato vaso per caso, a meno di risolvere correttamente il sistema proposto da repne scasb. Di conseguenza ho preferito limitarmi ad un ragionamento puramente logico per cercare di dimostrare come il "caso particolare linux" si possa ricondurre al caso generale "è sempre possibile creare un compilatore C++ (eventualmente non nativo) almeno efficiente quanto un compilatore C, e usare il linguaggio con la stessa efficienza", cercando di evidenziare che non volevo spingermi oltre con la logica. :)
Originariamente inviato da asbuni
Scusate se mi intrometto.
Come ho già scritto in un messaggio precedente, scuse di questo tipo io non le accetto... :nonsifa: :p
Ho capito il tuo punto di vista. Ovviamente, il C++ (usato come C++) limita il raggio d'azione e le possibilità di intervento del programmatore, mentre il C consente di gestire più in dettaglio certi aspetti; d'altro canto, però, la maggiore libertà concessa dal C, in special modo laddove il programmatore non può limitarla, può avere effetti spiacevoli: lasciare libero accesso, ad esempio, ai dati di una struttura-oggetto a chiunque può essere rischioso e comportare forse anche qualche calcolo in più (piccolo esempio: la variabile modificata può assumere solo determinati valori, per cui devo verificarne la correttezza; ora, posso farlo in due modi: o all'interno di una set, che può si rallentare, ma ha il vantaggio di effettuare la verifica solo all'occorrenza, oppure devo farlo ogni volta che eseguo dei calcoli su quella variabile e, a seconda dei casi, potrei anche perdere più tempo); inoltre, aumenta la possibilità di compiere errori di logica - ricordo a tal proposito la vicenda di una sonda che non riusci ad agganciare l'orbita del pianeta cui era destinata per una virgola fuori posto in un ciclo (il codice era scritto in Fortran, se non ricordo male, che in quella versione forse non consentiva neanche la programmazione strutturata (non ne sono sicuro): salti condizionali gestiti a mano, una vera pacchia... se trovo quello spezzone di codice magari lo posto); personalmente mi piace molto, ad esempio, il controllo che fa Java sugli indici di un array: comporta forse qualche calcolo in più, ma è più sicuro.
Poi, qualche libertà si può avere anche in C++: sempre nell'esempio dell'accesso alle variabili di un oggetto, lo si può fare dichiarandole public, e se serve maggiore libertà o si ritiene di poter avere maggiori prestazioni, si può sempre scrivere alcune porzioni di codice in C. Ovviamente questo comporta delle scelte precise, e quindi un design più accurato su certi aspetti (e probabilmente anche tempi più lunghi); tuttavia, personalmente ritengo questo approccio preferibile, poichè consente al progettista di avere un maggior controllo sui possibili effetti collaterali: uso i costrutti e la libertà del C solo quando ritengo che serva e che non consenta agli altri di "far danni" a causa di una interazione sbagliata con l'oggetto e le sue strutture. Naturalmente è una questione di scelte e/o gusti (forse è meglio dire punti di vista). Se invece la libertà richiesta consistesse nella possibilità di implementare i meccanismi dell'ereditarietà, dell'overloading, ecc., con qualche "trucchetto" più intelligente di quanto si pensi faccia il C++ (con le sue limitazioni imposte), si può sempre ricorrere al traduttore C++ -> C (soluzione alla RatFor, se vogliamo, o alla "C++ neonato"), in modo da implementare i costrutti del C++ con gli espedienti migliori per ottenere un codice C molto performante (la questione si sposterebbe sulla ricerca del modo migliore per ottimizzare il linguaggio); il programmatore esperto, successivamente, potrebbe analizzare il listato C prodotto per verificare che corrisponda alle proprie esigenze o, per i suoi scopi, non sia possibile/preferibile modificare qualcosa. Resta comunque da verificare la validità di questa scelta, poichè un compilatore nativo C++ da un lato limita in parte la libertà (ma si può sempre programmare come in C, bisognerebbe anche valutare la possibilità di far produre ad un buon compilatore codice eseguibile ben ottimizzato partendo da un listato C-like), dall'altro i costrutti utilizzabili vengono ottimizzati (una volta tradotti in linguaggio macchina) ad un livello molto più basso, mentre il C, se da un lato aumenta la libertà nel gestire istruzioni, dati e strutture, dall'altro "passa" al compilatore degli oggetti che verranno trattati (per ottimizzare l'eseguibile) esattamente come tutti gli altri costrutti del C, secondo quanto previsto per la programmazione strutturata, ovvero la maggiore ottimizzazione rispetto al C++ si concretizza ad un livello più alto.
Di conseguenza, se è vero che C++ : C = C : Assembly (senza dare una validità rigidissima all'equivalenza), in un certo senso è vero anche che C_OO_compilato : C++_OO_compilato = C : Assembly (o meglio, come C_compilato o C++_compilato o Pascal_compilato : Assembly_programmato_da_esperto). In sostanza, fermo restando la necessità di operare delle scelte precise e molto più ponderate, ritengo possibile programmare anche un kernel in C++ con la stessa efficienza del C, eventualmente scrivendo porzioni di codice o interi moduli in C. Una soluzione ibrida potrebbe risolvere la contesa, compensando la minor libertà del programmatore con una maggiore robustezza dell'eseguibile scaturita dalla maggiore ponderazione di determinate scelte.
P.S. Non ti ho ancora risposto sull'altro thread perchè non ho avuto abbastanza tempo, sono stato in parte assorbito da questo e dovevo riflettere su alcune cose. Lo farò al più presto (sono sicuro che questo ti farà IMMENSAMENTE piacere :D:D:D)
Ciao a tutti.
cdimauro
21-10-2004, 07:49
Originariamente inviato da ilsensine
Ehi ehi piano, questa è diffamazione a mezzo stampa :D
Sarà il periodo: c'è gente che minaccia querele per quanto scritto in un giornalino locale che si occupa di politica... :asd:
Quello che dico è che "se non hai problemi a utilizzare driver proprietari (e io li ho), butta la tua ATI e compra una nvidia. Hanno driver fra i migliori nel mondo closed source, e (soprattutto) un atteggiamento cristallino e rispettoso nei confronti della comunità libera".
Qualcosa la ricordavo bene allora... ;)
Questo non vuol dire che le nvidia non hanno problemi: li hanno, ma molto meno di altri (e con un supporto alla risoluzione dei problemi migliore).
I problemi li hanno tutti, ma quel che dici mi conforta: non è tutto marcio il mondo closed source... :)
cdimauro
21-10-2004, 08:03
Originariamente inviato da repne scasb
Ho risposto a xeal su questo punto (vedi la risposta dello sviluppatore capo di EPSD riportata nella risposta di Xeal). In sostanza, volevo dimostrare (e dalla tua risposta intuisco, che sono stata fraintesa), che per esigenza "PARTICOLARI" un sorgente C, compilato da un compilatore C e da un compilatore C++, puo' essere diverso senza che ve ne sia colpa in chi ha sviluppato il compilatore (vedi risposta a xeal di Adrian Bennet). E qui ribadisco che sostenere che un compilatore e' malfatto esclusivamente in base al fatto che genera codice macchina diverso, senza conoscere le ragioni che hanno spinto gli sviluppatori a prendere una simile decisione e' offensivo del lavoro degli sviluppatori stessi (e se permetti avendone fatto parte m'irrito alquanto).
Ribadisco pure io e mi associo a quanto scritto da fek: non volevamo assolutamente essere offensivi. Se hai ravvisato diversamente, sono pronto a farti le mie scuse.
Tornando al merito della questione, il nocciolo della discussione era ben preciso: un compilatore C++ non può essere più inefficiente di un compilatore C, se gli diamo in pasto un sorgente C. E questo è stato dimostrato informalmente, tramite l'esempio descrittivo che abbiamo riportato fek e io; di ciò, se è il caso, posso anche riportati una dimostrazione formale, ma preferisco un esempio più pratico, da informatico. ;)
Anch'io ho scritto dei compilatori (per puro diletto, sia chiaro) sia a singola sia a passata multipla, per cui mi è abbastanza chiara la logica su cui si passa dall'interpretazione di un sorgente alla generazione del codice.
Il motivo per cui il compilatore EPSD ha dei risultati diversi, l'avete già ampiamente descritto e discusso, è che viene generato del codice addizionale per gestire eventualmente i costrutti propri del C++.
Per ovviare a tutto ciò, si potrebbe pensare di generare questo codice addizionale solamente all'occorrenza. In che modo? C e C++ sono compilatori a passata multipla (non potrebbe essere diversamente, vista la natura dei linguaggi, e al contrario di Pascal e derivati, che generalmente ne necessitano di una soltanto), per cui sarebbe sufficiente stabilire, dopo la prima analisi del codice, se e quali costrutti del C++ siano stati utilizzati, ed emettere :) il codice appropriato.
In questo modo rientriamo nei canoni della dimostrazione fornita: il compilatore sarà sempre e comunque più efficiente dell'equivalente C. Ergo, lo staff potrebbe concentrarsi esclusivamente su quello C++, perché è quello che da cui possono derivare maggiori benefici per il futuro.
Mi e' sfuggito "LX" (non la conoscevo, non conosco tutto),
E' normale: siamo sempre e comunque degli esseri umani... :)
se la CPU ha in "nativo" istruzioni MIN e MAX e' chiaro che hai ragione, e mi scuso se ho frainteso.
Non ti preoccupare: sicuramente t'era sfuggito.
Il compilatore deve riconoscere le strutture IF o ? e trattarle nel modo dovuto (azzardo un'ipotesi: non e' che nella documentazione si dice una cosa del genere: Per utilizzare le istruzione native MIN e MAX si consiglia di utilizzare la sintassi: = ? : ).
No, assolutamente: è una cosa che abbiamo scoperto tramite profiling del codice, ed è stata segnalata allo staff che si occupa del compilatore.
Chiaramente rientra la delusione.
:)
Nota a margine: Non trovo il datasheet della CPU utilizzata dal tuo compilatore VLIW LX per CPU VLIW LX. Mi dici esattamente qual'e' la CPU utilizzata in output dal compilatore?
LX è un'architettura VLIW sviluppata da STMicroelectronics e utilizzata ampiamente nei propri integrati. Il compilatore è stato sviluppato internamente, per cui non è disponibile al pubblico.
Penso che, comunque, dei datasheet dovrebbero essere disponibili sul sito di STM. Adesso non ricordo come recuperarli, ma forse dovresti cercare come VLIW o RISC se non trovi niente come LX.
cdimauro
21-10-2004, 08:05
Originariamente inviato da Ikitt_Claw
A malincuore, ma noi GNUcchi sopravviveremo. ;)
L'importante è che sopravviva la verità. :) Son contento che si sia capito come stiano realmente le cose...
cdimauro
21-10-2004, 08:10
Originariamente inviato da afterburner
Di Mauro non ha mai ragione Punto E' un assioma Punto
Non noto faccine, per cui suppongo che non si tratti di un complimento. :rolleyes:
Comunque, mi spiace averti distrutto un mito, ma come ho già detto ben più di una volta, a me piacere INFORMARE. E Linus ha "toppato" ben più di una volta in merito.
Qui nessuno mette in dubbio i suoi meriti e le sue capacità per quanto concerne la programmazione di un kernel (e dopo più di 13 anni di esperienza, vorrei ben vedere), ma con le sue strampalate affermazioni ha dimostrato, semmai ci fossero ancora dei dubbi, che non è un programmatore C++, che non conosce il linguaggio, e non conosce come funzionano i compilatori. Il punto lo metto io, adesso.
Di questo fattene una rattene una ragione, e pensa che, in ogni caso, è una persona che nel SUO lavoro è comunque un grande professionista.
cdimauro
21-10-2004, 08:15
Originariamente inviato da DjMix
Io penso che il più delle volte questi "notevoli interessi" sia una scusa per nascondere le porcate che si son fatte.. da come ho potuto vedere più volte in giro per ditte (daccordo, PMI, ma è la mia esperienza diretta. Per il resto mi fido della signature di ilsensine). Cmq, riguardo al nostro discorso, il dual-head delle ati funziona benissimo con driver OPEN SOURCE incluso in Xfree 4.4, come puoi vedere qui (http://www.xfree86.org/current/radeon.4.html). Davvero pensi che Nvidia andrebbe in fallimento a rivelare un pò di specifiche? Ma lasciamo stare le GPU, parliamo di chipset.. comè che il primo Nforce non han voluto dire NULLA? che poi è saltato fuori che il controller IDE funziona perfettamente col driver di un amd, e la scheda di rete.. un ragazzo ha fatto reverse engeneering e l'ha fatta funzionare lo stesso... ma davvero vuoi farmi credere che ci sono incredibili interessi dietro alle specifiche di una SCHEDA DI RETE????? Mi sento preso in giro se mi dicono così.
Posso farti un esempio, che vale più di mille parole: se nVidia mettesse in circolazione le specifiche o addirittura i sorgenti, secondo te chi chi se ne potrebbe avvantaggiare? Chi è che ha interesse a scrivere driver funzionali e compatibili con quante più applicazioni esistenti? Qualche nome? Ati, SIS (Xabre), Via (Unichrome, Deltachrome), Matrox, ecc..
Specialmente nel caso di case come SIS e Via, che si trovano in un mare di guai a causa dei loro scarsi driver, sarebbe come vincere un terno al lotto: tutto il lavoro è già stato fatto, e a costo zero.
repne scasb
21-10-2004, 08:42
repne scasb
21-10-2004, 09:51
Ikitt_Claw
21-10-2004, 10:17
Originariamente inviato da cdimauro
Posso farti un esempio, che vale piu di mille parole: se nVidia mettesse in circolazione le specifiche o addirittura i sorgenti, secondo te chi chi se ne potrebbe avvantaggiare?
Dipende di quale componente si sta parlando.
Nel caso di chip video, il caso piu` scottante, il tuo ragionamento sotto elencato fila, ma ad una condizione: che le tecniche inventate da nvidia (per i loro specifici chipset) siano riciclabili dai concorrenti con poco o nessuno sforzo. Questo, quantomeno da quanto sinora detto, non mi pare affatto scontato.
nel caso di componenti quali scheda ethernet, bridge agp, controller EIDE, la situazione, almeno dal mio punto di vista di GNUcco, pare ancora piu` imbarazzante (nel senso di eccesso di difesa).
Di come conoscere le specifiche dei chipset nvidia possa avvnataggiare ati o via, invece, mi sfugge del tutto.
Limite mio beninteso, infatti piu` indietro chiedevo lumi a fek...
Ikitt_Claw
21-10-2004, 10:19
Originariamente inviato da cdimauro
L'importante e che sopravviva la verit`a. :)
Quella sopravvive sempre, basta scavare un poco e non fermarsi alla prima scorza ;)
RaouL_BennetH
21-10-2004, 11:18
Originariamente inviato da asbuni
Scusate se mi intrometto. Vorrei, se mi è concesso, dare il mio piccolo contributo alla discussione su alcuni aspetti. Mi riferisco in particolare a cose scritte da fek, xeal, ilsensine. Forse sto per scrivere un mucchio di ovvietà ma mi sembrano sfumature attinenti al vostro dicorso, che vi sono sfuggite e che invece mi sembrano rilevanti
Sulla "diatriba" delle performance del C vs. il C++ in generale, a mio avviso state prendendo l'argomento per un verso che non è esattamente quello più adatto perchè non tiene nel dovuto conto (mi sembra) della principale differenza tra i due linguaggi, cioè il paradigma di programmazione e, soprattutto, i conseguenti "effetti collaterali" dovuti a questa differenza. Non è una questione di compilatore C o compilatore C++ più o meno nativi, e nemmeno una questione di linguaggio C (i suoi costrutti) che sia intrinsecamente più o meno veloce del linguaggio C++. E' invece una questione di metodo di programmazione. Programmare in C per un programmatore significa farsi carico personalmente della gestione di tutta una serie di problematiche che invece in C++ vengono "nascoste" poichè vengono risolte dal compilatore; di contro significa anche che il programmatore in C ha un maggiore controllo sul codice che verrà generato e di conseguenza sull'esatta entità e sequenza delle istruzioni che la macchina eseguirà. A causa della differenza del paradigma di programmazione il C++ inserisce rispetto al C tutta una serie di astrazioni per il programmatore paragonabili (in analogia, non in similitudine) alle astrazioni che il C inserisce rispetto all'Assembly e perciò le eventuali differenze di prestazioni che ci possono essere tra il codice generato a partire da un sorgente C da uno equivalente nelle funzioni ma generato a partire da un sorgente C++ (che sfrutti la OOP, ovviamente) sono essenzialmente dovute al "potere di controllo" che il programmatore ha sul codice generato. Nel C questo potere è maggiore che nel C++, di conseguenza un bravissimo programmatore C è POTENZIALMENTE in grado di ottenere SEMPRE un programma più performante rispetto a quello sviluppato da un altrettanto bravissimo programmatore C++. Per esempio è indubbio che in C si possono "emulare" tutte le funzionalità introdotte dal C++ e l'eventuale implementazione di queste funzionalità sarebbe totalmente nelle mani del programmatore anzichè del compilatore. Oltre a queste questioni di maggior "potere di controllo" sul codice generato (e quindi possibilità di scrivere codice ottimizzato per una certa situazione o contesto), il paradigma OOP introduce intrinsecamente per sua stessa natura altre fonti di potenziale rallentamento. Esempio: se da una parte l'utilizzo dell'ereditarietà può forse dare vantaggi prestazionali al C++ rispetto alla sua "emulazione" in C, dall'altra l'utilizzo dell'incapsulamento inserisce invece un "percorso forzato" ai dati in memoria che un programmatore C++ si trova a DOVER seguire, laddove invece il programmatore C può decidere di "prendere una scorciatoia" scrivendo/leggendo direttamente i dati di una struttura, senza passare per le famose getter/setter functions. Questo tanto per far l'esempio più semplice ed eclatante di un "percorso forzato" presente in C++ e che può causare (specie se la funzione getter/setter in questione non viene inline-izzata) prestazioni peggiori rispetto all'"accesso diretto"; un pò come la differenza che c'è nell'accedere alle periferiche tramite le porte I/O del processore invece che tramite DMA
Detto così si potrebbe dire che la programmazione in C++ non abbia scampo nel confronto prestazionale con la programmazione in C. Per "fortuna" del C++ le cose non stanno esattamente così perchè nel momento in cui si passa dalla teoria alla pratica molte cose, al solito, cambiano: un programmatore C, anche il migliore del mondo, che conosca a menadito sia il processore sia il compilatore che utilizza e sarebbe quindi perfettamente in grado di scrivere programmi C di valore prestazionale assoluto, nella maggior parte dei casi preferisce (giustamente) sacrificare un pò di prestazioni a favore di leggibilità, manutenibilità, estensibilità del programma; il più delle volte questo "modus-programmandi" in C finisce per portare il programmatore a scrivere codice sorgente che all'atto pratico "emula" quegli stessi meccanismi che un linguaggio OOP implementa nativamente, ed ecco che quindi il potenziale "vantaggio" iniziale del C rispetto al C++ tende a ridursi se non addirittura annullarsi (a seconda della bontà del programmatore nonchè ovviamente della bontà dei compilatori C e C++ usati). Ergo, a parità di bontà di programmatori e compilatori, le prestazioni di un linguaggio procedurale in genere rispetto ad uno OO direi che dipendono da se e come si sfruttano i rispettivi stili di programmazione
Per quanto riguarda lo specifico di usare il C++ per lo sviluppo di un kernel, il problema invece non è una questione di paradigma di programmazione dato che ad es. il kernel Linux ha chiaramente un approccio OO pur essendo scritto in C. In un kernel il fattore "potere di controllo" da parte del programmatore diventa cruciale, a differenza di un qualsiasi software in userspace, e quindi il C avrà sempre un vantaggio sul C++; alcune cose che in C sono ordinaria amministrazione, espressamente consentite e non di rado incoraggiate (e su cui gli odierni programmatori di kernel si appoggiano in maniera pesante per sveltire lo sviluppo) sono vere e proprie bestemmie in C++. Un paio di esempi: la tipizzazione a dir poco lasciva (Dio solo sa quante void* ci sono nel kernel di Linux) o la disinvoltura con cui un modulo del programma si "intromette" negli affari di un altro modulo andandone a modificare i dati. Sicuramente in C++ si potrebbe risolvere adeguatamente ed in maniera elegante tutte quelle problematiche, rendendo tra l'altro il codice sorgente più pulito, più lineare, più estensibile, ecc. ma ciò richiederebbe uno sforzo di re-designing prima ancora che di re-factoring. Molte cose che in C le puoi semplicemente fare, in C++ bisogna che il programmatore originario abbia PENSATO e previsto che un altro programmatore potrebbe avere necessità di farle e gli abbia quindi lasciato "la porta aperta" o al massimo socchiusa. Riprendendo un esempio che ilsensine aveva fatto a fek, quello del puntatore a funzione per la ioctl delle file_operations del kernel Linux che in C basta andare a sostituire se si vuole, la soluzione del pattern Visitor proposta da fek non è proprio equivalente perchè ciò che si fa realmente in C equivarrebbe in C++ al modificare la vtable di una classe, cosa che in C++ è ovviamente impossibile. La soluzione Visitor è solo un "workaround" in perfetto stile OOP a cui il C++ ti "costringe" e che rallenta l'esecuzione ma che soprattutto deve essere stata espressamente prevista da chi avesse scritto la classe "file" contenente il metodo "ioctl" a cui un altro programmatore volesse aggiungere un Visitor. Per generalizzare il concetto, in un kernel scritto in C un programmatore può alterare a posteriori ed anche al volo (inserendo e togliendo moduli del kernel) il comportamento di un'altra parte del kernel, senza che il programmatore di quell'altra parte abbia previsto espressamente di consentirlo. In C++ questo non è possibile e se questa "imposizione" del C++ è generalmente auspicabile e induce certamente ad un Buon Design, può risultare un ostacolo per un kernel che voglia essere "metamorfico" come è Linux. Ovviamente anche in C ci sono limiti ben precisi ma sono molto più permissivi di quelli del C++ ed inoltre è più rapido e semplice aggirarli o toglierli all'occorrenza
Comunque penso che avendo un ottimo compilatore C++ NATIVO (cioè che scriva direttamente codice macchina) si potrebbe scrivere un kernel dalle prestazioni più che ottime o persino equivalenti a quelli di oggi scritti in "C con approccio OO", ma avrebbe quelle rigidità intrinseche al C++ (essendo un linguaggio OOP) che potrebbero non favorire uno sviluppo modello "bazar" quale è quello di Linux, mentre sarebbe l'optimum per lo sviluppo modello "cattedrale" quali ad es. QNX o anche Windows. Questo lo dico riflettendo su una mia personale esperienza che non so se capiti anche a voi: quando devo CAPIRE un software scritto da altri ci riesco meglio se è scritto in C mentre quando devo SCRIVERE un software mi trovo meglio (molto meglio) in C++
Ho letto e riletto il tuo post, che trovo interessantissimo e pieno di altri spunti imho, e volevo farti una domanda:
Stando a quello che dici in merito al tipo di controllo molto più diretto relativo a programmatore-linguaggio_C, non potrebbe questo essere un limite (umano) insito stesso del linguaggio? (è una blanda provocazione :) )
cdimauro
21-10-2004, 12:48
Originariamente inviato da repne scasb
Sono in attesa del datasheet completo della tecnologia ma, da una prima analisi, "sembrerebbe" che la tecnologia sia incentrata su:
1) Bassi consimi per i dispositivi.
2) ISA efficiente grazie ad opcode compatti (con codice ideale da inserire in dispositivi embedded dotati di poca memoria).
Esatto.
3) ISA decomponibile e ricomponibile tipico delle CPU old-RISC.
Se vuoi dire che l'ISA è customizzabile, ossia che è possibile inserire nuovi opcode "alla bisogna", sì. E i tool di sviluppo facilitano MOLTO questa cosa, permettendo "cose turche" :D, ma di questo non posso parlare sfortunatamente (dovrei chiedere a qualcuno il permesso).
4) Adatto al processamento di stream di informazioni (ideale per un DSP), grazie alla possibilita' di eseguire simultaneamente un operazione float/(int?) su piu' dati in ingresso.
Mumble. Non ricordo questo dettaglio francamente. :(
Sembrerebbe funzionare bene per decomprimere un MP3, un flusso video MPEG-2, una JPEG..., in generale trattare "in qualche modo" un flusso "continuo" di informazioni in ingresso restituendo un output "continuo".
Esattamente. E' utilizzatissimo per sistemi di elaborazione di immagini (tipo fotocamere digitali), ed è stato usato anche per un decoder MPEG4.
Aspetto il datasheet completo per un analisi piu' dettagliata, ma questi dispositivi sembrerebbero dei VLIW poco "ortodossi".
In che senso? "Necessito input" - Johnny 5. :)
cdimauro
21-10-2004, 12:52
Originariamente inviato da Ikitt_Claw
Dipende di quale componente si sta parlando.
Nel caso di chip video, il caso piu` scottante, il tuo ragionamento sotto elencato fila, ma ad una condizione: che le tecniche inventate da nvidia (per i loro specifici chipset) siano riciclabili dai concorrenti con poco o nessuno sforzo. Questo, quantomeno da quanto sinora detto, non mi pare affatto scontato.
Certamente ci sono le differenze architetturali da tenere in considerazione, ma nel caso peggiore (leggi: non si può prendere una parte di codice) posso sempre servire per capire in che modo hanno risolto un determinato problema. E non è poco.
nel caso di componenti quali scheda ethernet, bridge agp, controller EIDE, la situazione, almeno dal mio punto di vista di GNUcco, pare ancora piu` imbarazzante (nel senso di eccesso di difesa).
La penso esattamente come te, in questi casi: c'è poco da nascondere, visto che si tratta di tecnologie arcinote.
Di come conoscere le specifiche dei chipset nvidia possa avvnataggiare ati o via, invece, mi sfugge del tutto.
Limite mio beninteso, infatti piu` indietro chiedevo lumi a fek...
Il limite è anche il mio: non progetto chipset e non so quali "insidie" si possono nascondere in ciò. Certamente il fatto che la prima soluzione di nVidia fosse alquanto bacata è indice che non sia una cosa molto semplice...
cdimauro
21-10-2004, 13:00
Originariamente inviato da RaouL_BennetH
Stando a quello che dici in merito al tipo di controllo molto più diretto relativo a programmatore-linguaggio_C, non potrebbe questo essere un limite (umano) insito stesso del linguaggio? (è una blanda provocazione :) )
La struttura del linguaggio potrebbe effettivamente essere un problema, ma C e C++ sono "parenti stretti", per cui non dovrebbe essere un limitazione provocata dal linguaggio. Per lo meno questa è l'impressione che ho.
A mio avviso la soluzione ideale per i linguaggi C-like è rappresentata dal C++, perché permette di fare tutto ciò che si fa normalmente in C, e ben altro. E' chiaro che è uno strumento, e necessita di un'approfondita conoscenza per impare a conoscerlo bene e usare sapientemente le svariate possibilità "espressive" che permette.
In poche parole: se serve efficienza, si può procedere come si farebbe normalmente in C, andando a "rovistare" i moduli altrui senza pietà. ;) Si potrebbe anche pensare di giocare sporco e andare a toccare le vtable, ma non è una soluzione portabile (oddio: neppure l'uso di puntatori come interi e viceversa lo è, ma è una pratica molto diffusa :D) ed è eticamente scorretta.
repne scasb
21-10-2004, 13:58
Originariamente inviato da xeal
Ho capito il tuo punto di vista. Ovviamente, il C++ (usato come C++) limita il raggio d'azione e le possibilità di intervento del programmatore, mentre il C consente di gestire più in dettaglio certi aspetti; d'altro canto, però, la maggiore libertà concessa dal C, in special modo laddove il programmatore non può limitarla, può avere effetti spiacevoli
Esatto, questa è la differenza sostanziale che c'è non solo tra C e C++ ma anche tra qualsiasi linguaggio procedurale e un qualsiasi linguaggio OO. Più in generale, è la differenza che c'è tra qualsiasi linguaggio di più basso livello rispetto ad un altro linguaggio di più alto livello. Per fare un altro parallelismo, prendiamo il PHP: esso non è più lento del C semplicemente perchè è interpretato (si potrebbe benissimo compilare direttamente da un sorgente PHP), bensì perchè è di più alto livello, nasconde alcune cose al programmatore, facilitandogli la vita ma allo stesso tempo imponendogli certi comportamenti pre-determinati. E più i "comportamenti pre-determinati" sono general-purpose e più è facile che non siano ottimizzati per nulla in particolare, introducendo quindi penalizzazioni di vario tipo che non ci sarebbero con un linguaggio di più basso livello nelle mani del programmatore. Nel caso del C vs. C++ i "comportamenti pre-determinati" riguardano la programmazione OO, l'RTTI, le eccezioni, l'overloading, ... , nel caso del C vs. PHP riguardano invece altre "specificità" del PHP, tipo il suo essere orientato al web, tutta la serie di librerie più o meno builtin che si porta dietro, ecc.
la variabile modificata può assumere solo determinati valori, per cui devo verificarne la correttezza; ora, posso farlo in due modi: o all'interno di una set, che può si rallentare, ma ha il vantaggio di effettuare la verifica solo all'occorrenza, oppure devo farlo ogni volta che eseguo dei calcoli su quella variabile e, a seconda dei casi, potrei anche perdere più tempo
Questo qualche volta è vero ma più spesso è vero l'inverso: usare le setter functions può introdurre dei controlli in più che manovrando direttamente i dati potresti evitare (perchè magari li hai già effettuati in precedenza, o semplicemente perchè tu programmatore sei sicuro che in quel punto del programma i dati sono certamente consistenti); una setter function con visibilità public che si rispetti dovrebbe SEMPRE effettuare verifiche di consistenza sugli argomenti passati prima di variare le variabili private della classe e qui può nascere il possibile "problema" perchè non di rado ci sono situazioni dove devi chiamare più setter functions in sequenza ed in quei casi l'overhead dovuto alle verifiche di consistenza effettuate dalle setter functions può diventare esoso in quanto potrebbe essere sufficiente fare le verifiche una volta sola all'inizio della sequenza di chiamate
Poi, qualche libertà si può avere anche in C++: sempre nell'esempio dell'accesso alle variabili di un oggetto, lo si può fare dichiarandole public
Certo, ma allora cominci a violare il paradigma di programmazione OO. Ecco perchè dicevo che la differenza di velocità non è una questione prettamente di linguaggio, bensì di stile di programmazione. Più rendi public le proprietà di una classe, più ti sposti ad una programmazione non OO, quindi ti avvicini più al C e al "suo" stile di programmazione
e se serve maggiore libertà o si ritiene di poter avere maggiori prestazioni, si può sempre scrivere alcune porzioni di codice in C.
Heh, bisogna vedere in che punto del sistema software fare ciò. Quando si progetta un sistema software, specie se prevede pezzi di codice scritti NON in OOP, puoi trovarti in situazioni dove la decisione implementativa che hai preso per un determinato modulo influenzi la decisione da prendere per un altro modulo, con il rischio di ritrovarti di fronte a un "domino". Potresti ritrovarti nella situazione per la quale, in seguito a dipendenze tra i moduli, tutto il software debba essere scritto in C. D'altro canto anche in C++, quando sei in scope di classe, proprio per non penalizzare inutilmente le prestazioni dovresti programmare comunque in "stile C" : basta mantenere consistenti le invarianti di classe stabilite dal costruttore della classe medesima. Cioè in ambito di classe si torna generalmente alla programmazione procedurale
Di conseguenza, se è vero che C++ : C = C : Assembly (senza dare una validità rigidissima all'equivalenza), in un certo senso è vero anche che C_OO_compilato : C++_OO_compilato = C : Assembly (o meglio, come C_compilato o C++_compilato o Pascal_compilato : Assembly_programmato_da_esperto).
Direi che la proporzione "C_OO_compilato : C++_compilato = Assembly_programmato_da_esperto : C/C++/Pascal_compilato" rispecchi meglio il mio punto di vista. Nota le due differenze rispetto alla tua proporzione:
1) per me è superfluo specificare "C++_OO" perchè se dico "C++" ho già detto implicitamente che il sorgente è scritto in OOP; questo perchè associo in maniera inscindibile il C++ al fatto che è un linguaggio OO e non mi interessa se è anche un linguaggio totalmente compatibile a livello di sorgente con il C; questa la considero una caratteristica esclusivamente "ausiliaria". Se cominciassi a scindere il C++ dal suo essere OO non farei altro che trasformarlo in C. Sarebbe come se al PHP gli volessi togliere le variabili non tipizzate, le variabili contraddistinte dal $ iniziale, le variabili che rende disponibili automaticamente (quando usato come CGI), le sue specifiche funzioni di I/O, ..., non avrebbe più senso chiamarlo "PHP", tanto varrebbe chiamarlo "C interpretato"
2) la seconda parte della proporzione ha i termini invertiti rispetto alla tua, in quanto un compilato generato da sorgenti scritti in "C con approccio OO" può avere implementate le medesime caratteristiche di ereditarietà, incapsulamento, polimorfismo equivalenti a quelle generate dal compilatore C++, con la differenza che sarebbero state "ricamate" a mano dal programmatore in persona sulla misura delle esigenze specifiche di quel software e quindi potrebbe averle fatte con quella cognizione che gli consente di raggiungere prestazioni e dimensioni dell'codice oggetto generato proporzionalmente uguali a quelle che un programmatore Assembly può raggiungere rispetto ad un qualsiasi compilato generato da un qualsiasi linguaggio di più alto livello
P.S. Non ti ho ancora risposto sull'altro thread perchè non ho avuto abbastanza tempo, sono stato in parte assorbito da questo e dovevo riflettere su alcune cose. Lo farò al più presto (sono sicuro che questo ti farà IMMENSAMENTE piacere :D:D:D)
Urca! suona come la frase di uno che ha in serbo qualcosa di VERAMENTE grosso!! :D
Originariamente inviato da RaouL_BennetH
Stando a quello che dici in merito al tipo di controllo molto più diretto relativo a programmatore-linguaggio_C, non potrebbe questo essere un limite (umano) insito stesso del linguaggio? (è una blanda provocazione :) )
Per "limite umano" intendi che le potenzialità di programmazione in C sono "limitate" dal programmatore (umano) o che il linguaggio C è limitato per via dei "limiti" del suo ideatore (anch'egli umano)?
RaouL_BennetH
22-10-2004, 00:08
Originariamente inviato da asbuni
Per "limite umano" intendi che le potenzialità di programmazione in C sono "limitate" dal programmatore (umano) o che il linguaggio C è limitato per via dei "limiti" del suo ideatore (anch'egli umano)?
Forse ho sbagliato termine, più che limite, intendevo "rischio di introduzione di concetto umano", inteso come rischio di essere più soggetto a distrazioni o metodi errati di ragionamento da parte del programmatore.
Mi spiego meglio (ci provo):
Se non ho capito male, in C++ ci sono "metodi" propri del linguaggio che evitano, o meglio, dovrebbero evitare al programmatore di "preoccuparsi" troppo di determinati comportamenti, mentre, in C questo controllo è affidato in massima parte al programmatore, quindi, più soggetto ad errori umani che non del linguaggio stesso.
Originariamente inviato da repne scasb
Senza moltiplicazione con uno shift:
max=b-((unsigned)(b-a)&(-((unsigned)(b-a)>>SIGN_POS)));
Eh, no! Così non vale! :O :p :D
Così è molto simile a quello che avevo pensato io :D
Ok, avrei dovuto sriverlo, invece di descrivere vagamente quello che intendevo, mea culpa:
max = (((b-a) >> SIGN_POS) & (a-b)) + b;
Chiaramente funziona solo se il right shift risultante è di tipo artitmetico (mi è sembrato di capire dalle tue risposte che lo sia, l'ho provato e funziona; mi è rimasto un piccolo dubbio sulla possibile dipendenza dall'implementazione del compilatore (il Kernighan Ritchie parla solo di implementazione dipendente dalla macchina per lo standard ANSI)). In caso contrario, dovrei reinserire il casting e negare il risultato prima dell'and (copiando dalla tua versione :D), quindi aggiungerei una complementazione al codice risultante (diversamente ce n'è solo una in entrambe le forme).
Alla possibilità di usare due shift non avevo proprio pensato. Caio :)
Originariamente inviato da asbuni
Questo qualche volta è vero ma più spesso è vero l'inverso: usare le setter functions può introdurre dei controlli in più che manovrando direttamente i dati potresti evitare (perchè magari li hai già effettuati in precedenza, o semplicemente perchè tu programmatore sei sicuro che in quel punto del programma i dati sono certamente consistenti); una setter function con visibilità public che si rispetti dovrebbe SEMPRE effettuare verifiche di consistenza sugli argomenti passati prima di variare le variabili private della classe e qui può nascere il possibile "problema" perchè non di rado ci sono situazioni dove devi chiamare più setter functions in sequenza ed in quei casi l'overhead dovuto alle verifiche di consistenza effettuate dalle setter functions può diventare esoso in quanto potrebbe essere sufficiente fare le verifiche una volta sola all'inizio della sequenza di chiamate
Chiaramente dipende da quante volte bisogna fare modifiche ai dati dall'esterno e quante invece chiamare le funzioni che elaborano quei dati. Comunque, non riesco a seguire del tutto il tuo ragionamento: parli di chiamare più setter sugli stessi dati o su dati diversi? Credo la seconda, poichè nella prima non avrebbe molto senso: o controlli il dato prima di scriverlo nella variabile sempre, oppure mai, una volta si e una no non evita problemi di consistenza, aggiunge solo un controllo superfluo (a meno che non si abbia la certezza solo su alcuni dati). Nel secondo caso, cioè dovendo settare più variabili prima di eseguire determinate operazioni (chiamando i metodi opportuni), le setter potrebbero rallentare rispetto ad un controllo preliminare con settaggio diretto in quanto introducono una indirezione, ma potrebbero esserci alternative in questo caso: si potrebbero settare tutti i valori chiamando un costruttore opportuno (se il setting deve avvenire alla creazione dell'oggetto), oppure si possono sempre prevedere (ovviamente occorre uno sforzo di design in questo senso) dei metodi che settano gruppi di variabili o tutte le variabili per le quali è previsto il setting, eventualmente stabilendo un modo (tipo dei valori particolari) per individuare quali variabili non vanno modificate (anche se introdurrebbe un controllo in più). Naturalmente ci sarebbero ancora delle limitazioni, ma in alcuni casi può essere un buon compromesso. Inoltre, quando si lascia libero accesso agli attributi di un oggetto, si dovrebbe effettuare comunque un controllo nei metodi dell'oggetto stesso (il che potrebbe appesantire l'elaborazione più delle setter), poichè affidarsi a un controllo esterno, o addirittura ritenere che non sia necessario alcun controllo è rischioso, soprattutto quando l'oggetto/modulo va ad interagire con oggetti/moduli progettati da altri (ma anche lo stesso programmatore potrebbe commettere degli errori). Ovviamente è sempre una questione di scelte: posso cercare le prestazioni assolute a scapito della consistenza dei dati e della stabilità (qualora le mie previsioni sulla correttezza dell'interazione trai vari moduli si rivelassero errate), oppure avere una maggiore cura dei controlli e della sicurezza, mettendo in conto un potenziale calo delle prestazioni.
Certo, ma allora cominci a violare il paradigma di programmazione OO. Ecco perchè dicevo che la differenza di velocità non è una questione prettamente di linguaggio, bensì di stile di programmazione. Più rendi public le proprietà di una classe, più ti sposti ad una programmazione non OO, quindi ti avvicini più al C e al "suo" stile di programmazione
Si, ma d'altra parte anche il C, laddove si cerchino di "emulare" oggetti, si avvicina allo stile di programmazione del C++, quindi vi è una sorta "snaturamento" del linguaggio rispetto alle sue origini procedurali che inevitabilmente lo "appesantisce" (introducendo almeno in parte quelle stesse astrazioni che caratterizzano il C++). Se questo si fa evidentemente qualche beneficio dovra pur esserci nella ricerca di un compromesso tra i due paradigmi: se questo ha senso in C, perchè non potrebbe averne in C++? Con la differenza che in C alcune "libertà" non si possono (o sono difficili) da limitare, mentre in C++ (e in generale in un linguaggio OO, presente o "futuribile") quelle stesse libertà devono essere ricercate, quindi, a mio avviso, la ricerca di un compromesso a partire dal linguaggio più "restrittivo" non può che giovare alla robustezza dell'intero sistema software creato, poichè metterebbe maggiormente l'accento sui pro e i contro della concessione di quelle libertà, spingendo a ponderare meglio ogni scelta implementativa. Naturalmente ciò comporta un maggiore (anche molto maggiore) sforzo in fase di design. A proposito di compromessi: non ho ancora capito se in C++ è possibile utilizzare/creare meccanismi (simil-Java) tipo l'accesso al package, ovvero definire variabili e classi in modo che siano accessibili a tutte le classi di una certa libreria, ma all'esterno siano viste come private. Potrebbe essere un buon metodo per organizzare i vari moduli in gruppi coerenti e stabilire una correlazione più netta in base alle dipendenze e interrelazioni, in modo da attribuire dei privilegi d'accesso speciali laddove sia necessario (in un certo senso è come consentire l'accesso a campi protected di un oggetto a classi non direttamente derivate, o non derivate affatto, da quella in questione).
2) la seconda parte della proporzione ha i termini invertiti rispetto alla tua, in quanto un compilato generato da sorgenti scritti in "C con approccio OO" può avere implementate le medesime caratteristiche di ereditarietà, incapsulamento, polimorfismo equivalenti a quelle generate dal compilatore C++, con la differenza che sarebbero state "ricamate" a mano dal programmatore in persona sulla misura delle esigenze specifiche di quel software e quindi potrebbe averle fatte con quella cognizione che gli consente di raggiungere prestazioni e dimensioni dell'codice oggetto generato proporzionalmente uguali a quelle che un programmatore Assembly può raggiungere rispetto ad un qualsiasi compilato generato da un qualsiasi linguaggio di più alto livello
Siamo proprio sicuri che le cose stiano così? Cioè, può anche darsi, le capacità del programmatore potrebbero essere tali da permettergli di ottimizzare notevolmente i costrutti, però ho qualche perplessità, perchè si tratta di ottimizzazioni ad alto livello rispetto a quelle che può fare un compilatore che utilizzi pattern specificamente studiati. Considera ad esempio i test di repne scasb sull'algoritmo per la determinazione del massimo tra due numeri: i primi dimostrano come con scelte oculate un programmatore possa limare le prestazioni, gli altri come sia possibile limarle maggiormente operando a basso livello (bassissimo, considerando che le istruzioni assembly sono direttamente traducibili in linguaggio macchina). Cionondimeno, nel progettare un compilatore si può decidere di ricercare nei sorgenti istruzioni riconducibili alla ricerca del massimo (come fa il compilatore per VLIW LX di cui parlava Cesare) e sostituirle, ad esempio, con l'ultima routine descritta da repne (per x86). Proviamo a rapportare il confronto al caso C/C++: un programmatore C agisce obiettivamente ad un più basso livello, per cui può sicuramente limare qualcosa, però si ferma inevitabilmente ad ottimizzazioni sul linguaggio, ovvero sull'implementazione ad alto livello dei costrutti di base per la gestione degli oggetti, mentre un compilatore C++, pur partendo da costrutti di alto livello ed astrazioni meno ottimizzati (in particolare meno ottimizzati per scopi specifici), potrebbe tradurli in linguaggio macchina con un'implementazione (studiata appositamente) sicuramente (o potenzialmente) migliore di quella che potrebbe fornire un compilatore C (in quanto studiato per ottimizzare costrutti diversi). Chiaramente non mi riferisco alla velocizzazione dell'accesso ai campi di una struttura-oggetto bypassando le setter functions previste dal paradigma OOP e a "tricks" simili, poichè li considero scelte "di comodo" legate all'esigenza/alla scelta di prediligere ora l'uno, ora l'altro paradigma, ora di creare un ibrido (sostanzialmente non vedo grosse differenze tra creare e gestire oggetti in C e infrangere o seguire in maniera "elastica" il paradigma OOP); mi riferisco piuttosto a tutti i meccanismi legati alle proprietà specifiche degli oggetti, ovvero l'ereditarietà, il polimorfismo, ma anche l'overloading (che a rigore potrebbe caratterizzare anche un linguaggio procedurale).
Tutto, ovviamente, IMHO.
Originariamente inviato da RaouL_BennetH
Forse ho sbagliato termine, più che limite, intendevo "rischio di introduzione di concetto umano", inteso come rischio di essere più soggetto a distrazioni o metodi errati di ragionamento da parte del programmatore.
Mi spiego meglio (ci provo):
Se non ho capito male, in C++ ci sono "metodi" propri del linguaggio che evitano, o meglio, dovrebbero evitare al programmatore di "preoccuparsi" troppo di determinati comportamenti, mentre, in C questo controllo è affidato in massima parte al programmatore, quindi, più soggetto ad errori umani che non del linguaggio stesso.
E' esatto. La maggiore o minore libertà lasciata da un linguaggio a un programmatore comporta inevitabilmente dei pro e dei contro: i pro consistono in una maggiore espressività e potenza del linguaggio stesso nel gestire in dettaglio l'implementazione di un algoritmo, i contro nella maggiore possibilità di compiere errori di logica o rendere il codice più complicato da leggere, modificare, correggere, di dover fronteggiare effetti collaterali, ovvero non previsti. Sono insidie presenti in ogni scelta che il programmatore compie, dalla scelta di usare la ricorsione o l'iterazione, alla gestione dei puntatori, all'uso di variabili globali (non tutti i linguaggi esistenti o esistiti lo consentono); possono esserci conseguenze anche nel passagio di variabili a una funzione per riferimento, d'altra parte, impedire al programmatore di compiere queste scelte significa limitare il suo controllo sul programma e rinunciare a tutti i lati positivi. E' una questione di scelte progettuali che caratterizzano tutti i linguaggi.
Il C++, del resto, è uno strumento molto potente ed espressivo, che usato nel modo giusto può consentire comunque un notevole controllo, ma allo stesso tempo può portare a delle "complicazioni": ad esempio, in C++ (come in Ada, un linguaggio derivato dal Pascal), è possibile ridefinire gli operatori per esigenze particolari, il che può essere molto utile, ma può anche creare dei problemi. Tu potresti definire un oggetto che rappresenta le frazioni come una coppia di numeri e definire tutte le operazioni possibili (ad esempio, puoi stabilire che l'operatore '+' tra due frazioni si effettua calcolando il minimo comun divisore, che diventa il denominatore, ecc.); puoi stabilire che per stampare a video l'oggetto devono essere stampati il numeratore e il denominatore separati dal simbolo '/', ecc. Però, se dimentichi di stabilire delle regole per sommare una frazione e un intero, o un float, ti ritrovi con qualche problema. Altri linguaggi più recenti, come Java (che a sua volta è derivato dal C++), non consentono di ridefinire gli operatori. Java non consente neanche di gestire direttamente i puntatori: non si può accedere all'indirizzo delle variabili "elementari" (ad esempio, non puoi modificare il valore di un intero con una funzione), mentre le variabili che rappresentano gli oggetti sono puntatori "mascherati", gestiti in maniera semplificata rispetto agli altri linguaggi. Ovviamente questo ha dei pro e dei contro: per riprendere un esempio dai discorsi di fek e ilsensine, non si può allocare la memoria in maniera diretta e scegliendo il modo in cui farlo, cosa che è necessaria per un kernel.
Potreste evitare di settare il flag
[x] Notifica per mezzo di e-mail
ogni qualvolta rispondete a questo tread?
repne scasb
22-10-2004, 08:35
RaouL_BennetH
22-10-2004, 15:19
Originariamente inviato da xeal
E' esatto. La maggiore o minore libertà lasciata da un linguaggio a un programmatore comporta inevitabilmente dei pro e dei contro: i pro consistono in una maggiore espressività e potenza del linguaggio stesso nel gestire in dettaglio l'implementazione di un algoritmo, i contro nella maggiore possibilità di compiere errori di logica o rendere il codice più complicato da leggere, modificare, correggere, di dover fronteggiare effetti collaterali, ovvero non previsti. Sono insidie presenti in ogni scelta che il programmatore compie, dalla scelta di usare la ricorsione o l'iterazione, alla gestione dei puntatori, all'uso di variabili globali (non tutti i linguaggi esistenti o esistiti lo consentono); possono esserci conseguenze anche nel passagio di variabili a una funzione per riferimento, d'altra parte, impedire al programmatore di compiere queste scelte significa limitare il suo controllo sul programma e rinunciare a tutti i lati positivi. E' una questione di scelte progettuali che caratterizzano tutti i linguaggi.
Il C++, del resto, è uno strumento molto potente ed espressivo, che usato nel modo giusto può consentire comunque un notevole controllo, ma allo stesso tempo può portare a delle "complicazioni": ad esempio, in C++ (come in Ada, un linguaggio derivato dal Pascal), è possibile ridefinire gli operatori per esigenze particolari, il che può essere molto utile, ma può anche creare dei problemi. Tu potresti definire un oggetto che rappresenta le frazioni come una coppia di numeri e definire tutte le operazioni possibili (ad esempio, puoi stabilire che l'operatore '+' tra due frazioni si effettua calcolando il minimo comun divisore, che diventa il denominatore, ecc.); puoi stabilire che per stampare a video l'oggetto devono essere stampati il numeratore e il denominatore separati dal simbolo '/', ecc. Però, se dimentichi di stabilire delle regole per sommare una frazione e un intero, o un float, ti ritrovi con qualche problema. Altri linguaggi più recenti, come Java (che a sua volta è derivato dal C++), non consentono di ridefinire gli operatori. Java non consente neanche di gestire direttamente i puntatori: non si può accedere all'indirizzo delle variabili "elementari" (ad esempio, non puoi modificare il valore di un intero con una funzione), mentre le variabili che rappresentano gli oggetti sono puntatori "mascherati", gestiti in maniera semplificata rispetto agli altri linguaggi. Ovviamente questo ha dei pro e dei contro: per riprendere un esempio dai discorsi di fek e ilsensine, non si può allocare la memoria in maniera diretta e scegliendo il modo in cui farlo, cosa che è necessaria per un kernel.
:) grazie per il chiarimento.
Originariamente inviato da RaouL_BennetH
Forse ho sbagliato termine, più che limite, intendevo "rischio di introduzione di concetto umano", inteso come rischio di essere più soggetto a distrazioni o metodi errati di ragionamento da parte del programmatore.
Senza dubbio. Concordo e confermo tutto quanto ti ha scritto xeal. Non a caso il linguaggio più "libertino" per eccellenza, l'assembly, con il quale puoi raggiungere i massimi livelli di ottimizzazione sotto ogni aspetto, è anche il linguaggio in assoluto più error-prone
corretto ORRORE grammaticale
Originariamente inviato da repne scasb
L'unico limite, come hai correttamente notato, e' nello shift aritmetico a destra. Se la CPU per la quale compili non ha lo shift aritmetico a destra, sono guai per il compilatore
Già, al limite si dovrebbe scegliere sempre la strada "certa" (lo shift logico negato) oppure fare una compilazione condizionata.
(a sinistra lo shift aritmetico e logico potrebbero sembrare la stessa cosa ma alcune ISA differenziano le operazioni)
In genere cosa fanno? Mantengono invariato il bit meno significativo (che diventa quindi il valore inserito), inseriscono il valore del carry, fanno una rotazione o alterano semplicemente in modo differente i bit di stato (ne conosco una che fa così, ma in realtà è solo un progetto teorico per scopi didattici, niente che potrebbe avere un utilizzo reale)? O cos'altro?
Originariamente inviato da xeal
Comunque, non riesco a seguire del tutto il tuo ragionamento: parli di chiamare più setter sugli stessi dati o su dati diversi? Credo la seconda
Si, la seconda :) Comunque (non lo dico per contraddirti perchè sono sostanzialmente d'accordo con te, lo dico solo per il piacere di chiacchierare) non mi riferivo tanto ai casi semplici dove la setter function non fa altro che prendere il suo singolo argomento e piazzarlo nella variabile private (dopo il controllo di consistenza ;)), bensì ai casi di setter functions più complesse, che magari richiamano in cascata altre setter functions correlate, o che magari per i loro controlli di consistenza necessitano (direttamente o indirettamente tramite altre setter functions "incapsulate") di operazioni di natura "slow", tipo accesso ad un database, accesso ad un servizio di rete, attesa di input di qualche genere, ...
Si, ma d'altra parte anche il C, laddove si cerchino di "emulare" oggetti, si avvicina allo stile di programmazione del C++, quindi vi è una sorta "snaturamento" del linguaggio rispetto alle sue origini procedurali che inevitabilmente lo "appesantisce" (introducendo almeno in parte quelle stesse astrazioni che caratterizzano il C++).
Chiaramente si, infatti nel mio post iniziale, dopo aver decantato i "poteri" insiti allo stile di programmazione del C (non a oggetti), ho completato la mia analisi puntualizzando che alla fin fine anche in C si finisce col programmare "a oggetti" perdendo in ogni caso proprio quei "poteri", e si perdono tanto più quanto si usa il C con... uno stile da C++; lo scopo del mio intervento era quello di dare risalto al fatto che è lo stile di programmazione, più che il linguaggio o il compilatore, a fare la maggiore differenza in termini di prestazioni; era questa la sfumatura che mi era parso che fosse sfuggita e per la quale ho pensato di intervenire.
Comunque, per tornare all'argomento specifico che affrontavi in questo punto del post, anche qui non mi riferivo tanto al caso "generico" bensì a casi "specifici". Ovviamente fare una vera e propria "implementazione" completa e general-purpose dei costrutti OOP in C tali da equivalere funzionalmente quelli del C++ (ammesso che sia fattibile) non risulterebbe affatto in una ottimizzazione, tutt'altro. Quello che volevo dire è che tutte le caratteristiche chiave della OOP (ereditarietà, incapsulamento, polimorfismo) possono essere realizzate in forma più o meno primitiva e più o meno specifica per un dato contesto o anche singolarmente solo per un certo insieme di dati correlati, in modo da avere quelle funzionalità che ti servono e solo quelle, senza tutte quelle altre che non ti servono ma che, usando l'implementazione general-purpose del C++, ti tireresti dentro inevitabilmente. Riprendo l'esempio della ioctl delle file_operations nel kernel Linux: ciò che è stato realizzato all'uopo in quel caso non è nient'altro che una "primitiva" forma di polimorfismo: abbiamo una "struct file" che tra i membri ha una "struct file_operations" la quale ha esclusivamente puntatori a funzioni diverse per ciascuna delle cose che si può fare su un file (read, write, lseek, ...). Cioè abbiamo l'implementazione "manuale" (fatta dal programmatore) di quella che in C++ sarebbe stata la vtable della ipotetica classe "file" completa di metodi. Ovviamente è una vtable limitata perchè il polimorfismo è limitato alla sola "classe" (struttura dati) medesima cioè NON si ha un polimorfismo completo di tutti i crismi che la vera OOP esige (funzioni virtuali che fungono da intefaccia generica per l'utilizzo di sotto-classi più specifiche, ecc. ecc. sono sicuro che conosci alla perfezione questi concetti :)), si ha però quella "giusta" dose di possibilità di "polimorfismo" necessaria per ciò che serve in quel determinato contesto, nulla di più e nulla di meno. E' in quel senso che intendo che il C lascia più controllo al programmatore, anche quello di ricrearsi determinati "ambienti OOP" quel tanto che basta per quel che serve, senza tirarsi dietro tutte le altre cose che, volente o nolente, il compilatore C++ gli genererebbe se realizzasse la classe file scrivendo:
class file {
public:
file(...);
virtual ~file();
...
virtual int ioctl(int command, const IoctlParameter& arg) = 0;
virtual int ioctl(int command, IoctlParameter& arg) = 0;
...
private:
...
}
Poi è chiaro che usando il linguaggio C++, dato che lascia disponibile TUTTE le funzionalità del linguaggio C, si potrebbe scrivere lo stesso preciso identico sorgente così come è scritto ora nel kernel (in C), ed è chiaro che avrebbe (dovrebbe avere) le medesime prestazioni e dovrebbe generare proprio lo stesso identico codice oggetto (ho seguito l'altra parte della tua discussione con repne scasb e gli altri e sono d'accordo con te, cdimauro, e tutti coloro affermano che non dovrebbe esserci differenza) ma il punto è che in quel modo avresti programmato in C invece che in C++, perchè avresti usato lo stile procedurale invece che OO; l'unica differenza sarebbe stata che l'hai compilato lanciando "g++" invece che "gcc" ;)
A proposito di compromessi: non ho ancora capito se in C++ è possibile utilizzare/creare meccanismi (simil-Java) tipo l'accesso al package, ovvero definire variabili e classi in modo che siano accessibili a tutte le classi di una certa libreria, ma all'esterno siano viste come private
L'equivalente in C++ è proprio fare una classe apposita con quelle variabili/oggetti protected invece che private. Se mangi a "pane a Java" immagino la tua immediata spontanea obiezione: "ma così rischio di incasinarmi per trovare il punto esatto dove inserire questa classe base nella gerarchia". Non in C++, perchè in C++ è prevista l'ereditarietà multipla. Devi solo avere l'accortezza di derivare da quella classe sempre solo con modalità "public" o "protected", NON "private" altrimenti successive eventuali sotto-classi sarebbero viste come "esterne". Un'alternativa teorica sarebbe quella di usare la dichiarazione "friend" ma non è equivalente al "package" di Java perche devi mettere una dichiarazione "friend" nella classe che contiene le variabili per ognuna delle classi che devono poter accedere a quelle variabili
Siamo proprio sicuri che le cose stiano così?
Si, nei termini di specificità per singoli casi o contesti come quello che ho indicato sopra, si, sono sicuro
Cioè, può anche darsi, le capacità del programmatore potrebbero essere tali da permettergli di ottimizzare notevolmente i costrutti, però ho qualche perplessità, perchè si tratta di ottimizzazioni ad alto livello rispetto a quelle che può fare un compilatore che utilizzi pattern specificamente studiati
Senza dubbio, come dicevo, avere la pretesa di emulare in C tutte le funzionalità da OOP general-purpose nella loro interezza è semplicemente una battaglia persa. Sono perfettamente d'accordo con te e con quanto diceva fek in risposta alla tua ipotesi di creare una sorta di "pre-processore" C++ che genera un sorgente C da cui, finalmente, compilare. Si può considerare il C++ di più alto livello rispetto al C ma non fino a questo punto :)
[...] mi riferisco piuttosto a tutti i meccanismi legati alle proprietà specifiche degli oggetti, ovvero l'ereditarietà, il polimorfismo, ma anche l'overloading (che a rigore potrebbe caratterizzare anche un linguaggio procedurale)
Sull' overloading (anche qui non per contraddirti ma solo per chiacchierare) ho una piccola obiezione: l'overloading, sia degli operatori che delle funzioni, in fin dei conti è solo un meccanismo che sceglie in compile-time la funzione da richiamare, cioè l'indirizzo di memoria a cui far saltare il Program Counter del processore; un lavoro che nel C è svolto unicamente dal programmatore quando scrive il sorgente e che nel C++ è stato invece demandato al compilatore. Sinceramente non vedo possibilità di ottimizzazione da parte del C++ rispetto al C.
Per finire, e per esprimere meglio il mio personale apprezzamento per il C++ (che forse non è trapelato adeguatamente finora) nonchè per spezzare una lancia in favore di Torvalds, vorrei enfatizzare il fatto che "l'emulazione OOP" in C consiste nello sfruttare la (ridotta) espressività del linguaggio fino al punto in cui può arrivare, dopo di che si tratta di "modus-programmandi" (come l'avevo chiamato) che il programmatore si auto-impone. Si può benissimo programmare OO anche in Assembly perchè lo stile di programmazione (pomposamente detto "paradigma") è solo una questione di approccio. Un linguaggio che supporti nativamente, con i suoi costrutti, la OOP è un linguaggio che "impone" al programmatore di aderire a quello stile, ma è una imposizione benvenuta perchè quel che succede in realtà è che il linguaggio OO AIUTA il programmatore ad aderire a quello stile che è senz'altro il migliore tra quelli esistenti oggi. Un programmatore che si sia scontrato con problematiche di organizzazione da dare al codice sorgente, manutenibilità ed estensibilità del medesimo finisce comunque per programmare con quello stile, direi che viene spontaneo con qualsiasi linguaggio, anche in Assembly (per lo meno a me succede così), quindi un linguaggio che lo "imponga" espressamente è generalmente una manna dal cielo, almeno a mio modo di vedere. E gli eventuali (inevitabili) rallentamenti rispetto ad un linguaggio non OOP sono tutto sommato un non-problema perchè anche usando il C (ad esempio) in stile OO si ricreano più o meno fedelmente quegli stessi "colli di bottiglia" che si hanno usando il C++, percui tanto vale usare direttamente il C++, per lo meno vengo AIUTATO dalle "imposizioni" del linguaggio. Questo per me vale senz'altro come concetto generale, e vale anche per un kernel ma d'altro canto riconosco anche che un kernel è una "bestia" molto particolare, diversa da tutte le altre "bestie" (tipi di software) e quindi è anche giusto trattarla con attenzioni un pò diverse; ciò che ha detto Torvalds in quei 3 punti riportati da fek effettivamente è falso in generale ma vero in particolare: nel suo particolare, che è quello di esperto programmatore C che approva e adotta il paradigma OOP (lo dimostra il suo kernel) che però in un kernel preferisce implementare le caratteristiche di OO solo quando dove e come lo ritiene giusto, utile o necessario lui in quanto programmatore. Sinceramente, dopo essermi scontrato personalmente con la programmazione kernel-space ed essermi talvolta soffermato a pensare se e come si poteva fare una determinata cosa in C++ (cioè in OOP), la soluzione OO la trovavo pure però mi rendevo conto che non sarebbe ideale per quel contesto (non sempre, onestamente, ma neanche di rado), perchè avrebbe generato bloatware oppure rallentamenti indesiderabili oppure avrebbe scatenato uno di quei "dòmini" a cui mi riferivo ieri (per i quali volendo implementare qualcosa in C++ sarei stato costretto a rivedere anche altre parti correlate, e poi parti correlate a quelle correlate, e via discorrendo), percui non mi sento di dargli definitivamente torto
x asbuni
Naturalmente sono d'accordo anch'io che se non trovo il modo giusto per fare in C++ quello che posso/so fare meglio in C (perchè in C++ non c'è modo di farlo, o per farlo occorrono i salti mortali, oppure si può fare, e la soluzione è così semplice (sto solo facendo delle ipotesi) da sembrare, una volta trovata, quasi banale, ma per trovarla, se prima non si è mai affrontato quello specifico problema, occorre applicare ad ogni minimo dettaglio una logica così sottile che tutti i vantaggi sulla facilità di progetto, manutenibilità ecc. vanno a farsi benedire, allungano i tempi di sviluppo), si può scegliere, o almeno essere tentati, di lavorare solo in C, oppure di attenersi rigidamente allo stile OOP, con buona pace dei propositi di cercare il giusto compromesso.
Piuttosto volevo focalizzarmi sul fatto che partendo dal C++ e dal suo stile più consono, anche approdando ad una implementazione parziale o estesa, se non quasi totale, in C, a mio parere si potrebbe avere il vantaggio di aver ponderato (perchè costretti) meglio determintate scelte, come appunto l lasciare o meno libero accesso ai campi di una struttura da parte di moduli esterni, o decidere se è meglio ricercare le prestazioni pure, o invece mantenere un approccio più "conservativo" (nel senso di rispettare il paradigma OO in tutti i limiti che impone) per non correre rischi eccessivi. Mi rendo conto che potrebbe essere particolarmente difficile e provocare un effetto domino, ma credo che possa valere la pena di provarci (non dico che lo si debba fare per forza in Linux, è solo un discorso generale). Magari si potrebbe partire da una completa implementazione OO per poi cercare di limare le prestazioni, valutando l'opportunità di "infrangere" in proporzione variabile in base alle esigenze e agli esiti verificati il paradigma (magari prendendo come punto di riferimento tutti i trucchetti che sono stati studiati appositamente per evitare, in casi specifici, di produrre bloatware, sempre che si trovi qualcosa che faccia al caso nostro o da cui prendere spunto), fino a passare all'uso del C dove strettamente necessario; per cercare di limitare i problemi di incompatibilità tra gli oggetti dei due linguaggi si potrebbe tentare di stabilire a priori delle interfacce precise tra i vari moduli e usare una tipizzazione forte per limitare l'intervento sul codice dei moduli correlati (di modo che sia "sufficiente" ricompilarli) (chiaramente è un esempio banale, ci sono troppi aspetti da considerare che varierebbero caso per caso).
L'equivalente in C++ è proprio fare una classe apposita con quelle variabili/oggetti protected invece che private. Se mangi a "pane a Java" immagino la tua immediata spontanea obiezione: "ma così rischio di incasinarmi per trovare il punto esatto dove inserire questa classe base nella gerarchia". Non in C++, perchè in C++ è prevista l'ereditarietà multipla. Devi solo avere l'accortezza di derivare da quella classe sempre solo con modalità "public" o "protected", NON "private" altrimenti successive eventuali sotto-classi sarebbero viste come "esterne". Un'alternativa teorica sarebbe quella di usare la dichiarazione "friend" ma non è equivalente al "package" di Java perche devi mettere una dichiarazione "friend" nella classe che contiene le variabili per ognuna delle classi che devono poter accedere a quelle variabili
Uhm, non lo so, forse invece è proprio "friend" (devo essermi espresso male). Mi riferivo alla possibilità per un oggetto (istanziato) di accedere ai membri di un altro oggetto (istanziato) come se fossero public, quando invece, per le classi a cui non è consentito (comprese, in Java, le classi derivate che non fanno parte dello stesso package) quei membri non sono accessibili, come se fossero private. E' quello che in Java succede se non si indica lo specificatore di accesso (cosa che in C++ equivale a indicare private). Devo aver confuso un po' il discorso tirando in ballo i membri protected (volevo dire che si tratta di un meccanismo che rende i membri accessibili ad alcuni oggetti e non ad altri, in analogia all'ereditarietà dei membri protected; avrei dovuto specificare che mi riferivo alle istanze di una classe e non alla classe di per sè). Le clausole "friend" valgono solo ed esclusivamente per le classi indicate in maniera esplicita, o consentono l'accesso anche alle classi derivate da quelle specificate? Pensavo ad un meccanismo di questo tipo per creare un compromesso tra l'uso di funzioni setter e l'accesso diretto ai membri dell'oggetto: per le classi a cui è concesso, l'accesso è diretto, mentre per le altre è possibile solo mediante "setAttributo(...)".
Sull' overloading (anche qui non per contraddirti ma solo per chiacchierare) ho una piccola obiezione:
Sempre per chiacchierare (almeno finché un moderatore si rende conto che più di venti pagine di commenti sono ot e ci banna tutti :D), nei moduli del kernel com'è implementata (se c'è)? E l'ereditarietà?
percui non mi sento di dargli definitivamente torto
Ah, se per questo nemmeno io: si può discutere sulle alternative, con annessi pregi e difetti, sull'opportunità di adottarle, poi, alla fine, ognuno fa le sue scelte secondo determinati criterii, condivisibili o meno, ma non certo oggetto di giudizi "definitivi" ed assoluti. Un buon motivo per voler andare avanti programmando il kernel in C, senza neanche valutare l'ipotesi C++, potrebbe benissimo essere (e lo diceva anche fek) il non voler buttare alle ortiche il lavoro fatto e ripartire da zero. Ovviamente si può essere d'accordo o meno, si può pensare che con gradualità si possa procedere a un refactoring/re-design (ad esempio, partendo dall'interfaccia esterna, con dei wrapper ove necessario, e procedendo verso l'interno, secondo l'ipotesi di fek), oppure pensare che sia meglio lasciare le cose come stanno (ad esempio, perchè non si ha materialmente il tempo di farlo), o che non ne valga la pena, ma non si può certo dire "è sbagliato, punto e basta". Caso mai, mi lascia perplesso il fatto che a volte Torvalds abbia atteggiamenti proprio di questo tipo, e si lasci andare a sentenze poco "tecniche" (tipo "C++ sucks"), perchè personalmente da un programmatore esperto e capace quale a saputo dimostrarsi mi aspetterei un atteggiamento, come dire, più professionale e più possibilista, una maggiore propensione all'innovazione, dato che il suo giudizio fondamentalmente è rimasto fermo agli esiti del primo, fallimentare approccio al C++ (colpa dei compilatori del periodo principalmente).
cdimauro
23-10-2004, 09:24
Originariamente inviato da asbuni
Riprendo l'esempio della ioctl delle file_operations nel kernel Linux: ciò che è stato realizzato all'uopo in quel caso non è nient'altro che una "primitiva" forma di polimorfismo: abbiamo una "struct file" che tra i membri ha una "struct file_operations" la quale ha esclusivamente puntatori a funzioni diverse per ciascuna delle cose che si può fare su un file (read, write, lseek, ...). Cioè abbiamo l'implementazione "manuale" (fatta dal programmatore) di quella che in C++ sarebbe stata la vtable della ipotetica classe "file" completa di metodi. Ovviamente è una vtable limitata perchè il polimorfismo è limitato alla sola "classe" (struttura dati) medesima cioè NON si ha un polimorfismo completo di tutti i crismi che la vera OOP esige (funzioni virtuali che fungono da intefaccia generica per l'utilizzo di sotto-classi più specifiche, ecc. ecc. sono sicuro che conosci alla perfezione questi concetti :)), si ha però quella "giusta" dose di possibilità di "polimorfismo" necessaria per ciò che serve in quel determinato contesto, nulla di più e nulla di meno. E' in quel senso che intendo che il C lascia più controllo al programmatore, anche quello di ricrearsi determinati "ambienti OOP" quel tanto che basta per quel che serve, senza tirarsi dietro tutte le altre cose che, volente o nolente, il compilatore C++ gli genererebbe se realizzasse la classe file scrivendo:
class file {
public:
file(...);
virtual ~file();
...
virtual int ioctl(int command, const IoctlParameter& arg) = 0;
virtual int ioctl(int command, IoctlParameter& arg) = 0;
...
private:
...
}
Scusami, ma i metodi virtuali s'implementano comunque tramite chiamate indirette a funzioni, esattamente viene fatto in quelle struct. Non vedo differenze, o forse c'è qualcos'altro che mi sfugge?
ciò che ha detto Torvalds in quei 3 punti riportati da fek effettivamente è falso in generale ma vero in particolare: nel suo particolare, che è quello di esperto programmatore C che approva e adotta il paradigma OOP (lo dimostra il suo kernel) che però in un kernel preferisce implementare le caratteristiche di OO solo quando dove e come lo ritiene giusto, utile o necessario lui in quanto programmatore. [...]percui non mi sento di dargli definitivamente torto
Mi spiace, ma non condivido: c'è modo e modo di esprimere le proprie opinioni, e non è certo la prima volta che Linus si lascia andare a giudizi completamente campati per aria. In questo modo dimostra soltanto di essere ignorante in materia. Rimane un esperto nel SUO campo, ma sarebbe meglio per lui astenersi dall'uscire fuori dal seminato.
repne scasb
23-10-2004, 11:32
[b]Originariamente inviato da repne scasb[b]
Per esempio: se shiftare a sinistra di un bit equivale a moltiplicare per 2, si ha che con un registro a 16bit caricato con il valore 0FFFFh:
shift logico a sinistra di 0FFFFh -> 0FFFEh + carry=1 (in x86 accade sempre questo perche non c'e' lo shift aritmetico a sinistra).
ISA che ha lo shift aritmetico a sinistra:
shift logico a sinistra di 0FFFFh -> 0FFFEh + carry (o flag analogo) settato a 1.
shift aritmetico a sinistra di 0FFFFh=-1 e' equivalente a -1*2=-2 quindi 0FFFEh e il flag di carry settato a ZERO (non setta "giustamente" il carry).
Quello riportato e' solo un esempio, alcune volte ci sono differenze piu' sottili.
Capito. E' praticamente la stessa cosa che fa l'ISA esemplificativa cui accennavo (con la differenza che setta flag diversi: il risultato degli shift logici e delle rotazioni è sempre non negativo, non nullo, e senza overflow, mentre il riporto è settato "correttamente"; per gli shift aritmetici vengono alterati i flag). Non avevo mai prestato molta attenzione a questi dettagli.
Originariamente inviato da cdimauro
Scusami, ma i metodi virtuali s'implementano comunque tramite chiamate indirette a funzioni, esattamente viene fatto in quelle struct.
Senz'altro si (cioè, non mi sono mai addentrato a studiare i sorgenti del gcc ma credo che faccia così, io almeno farei così se dovessi fare un compilatore C++)
Non vedo differenze, o forse c'è qualcos'altro che mi sfugge?
L'unica differenza è che la "vtable" (nota le virgolette) fatta a mano dal programmatore in C è espressamente alterabile programmaticamente in run-time mentre la vtable (senza virgolette) generata compile-time dal C++ no. O mi sbaglio?
forse ho capito cosa intendi: probabilmente ho usato il termine "polimorfismo" in modo improprio per questo caso: si tratta più esattamente di uno di quei "metamorfismi" a cui mi riferivo nel mio primo post. Se era questo che ti era "sfuggito" è colpa mia. Pardon
Mi spiace, ma non condivido: c'è modo e modo di esprimere le proprie opinioni, e non è certo la prima volta che Linus si lascia andare a giudizi completamente campati per aria. In questo modo dimostra soltanto di essere ignorante in materia. Rimane un esperto nel SUO campo, ma sarebbe meglio per lui astenersi dall'uscire fuori dal seminato.
Si, sul modo sono d'accordo con te. Poteva starsene zitto oppure, per restare "estremista", dire semplicemente "il C++ mi sta antipatico punto" e avrebbe fatto una figura migliore. Oppure avrebbe potuto semplicemente usare una scusa che nessuno in onestà potrebbe controbattere: "ormai abbiamo iniziato così e per ora non abbiamo tempo per iniziare la conversione" cosa che è probabilmente vera a prescindere dalla possibile antipatia o ignoranza nei confronti del linguaggio
Originariamente inviato da xeal
Uhm, non lo so, forse invece è proprio "friend" (devo essermi espresso male). Mi riferivo alla possibilità per un oggetto (istanziato) di accedere ai membri di un altro oggetto (istanziato) come se fossero public, quando invece, per le classi a cui non è consentito (comprese, in Java, le classi derivate che non fanno parte dello stesso package) quei membri non sono accessibili, come se fossero private. E' quello che in Java succede se non si indica lo specificatore di accesso (cosa che in C++ equivale a indicare private).
Ho capito. Allora si, direi che l'unica è usare friend. Però, come ti dicevo, devi indicare esplicitamente ciascuna friend nella dichiarazione di ogni classe i cui oggetti devono avere i dati private leggibili dagli oggetti di un'altra classe. Se invece i dati da far leggere fossero anche da condividere (quindi con linkaggio static) allora nettamente meglio tornare all'altro metodo, con la differenza che, per evitare che classi ulteriormente derivate ereditino automaticamente i dati, dovresti derivare SEMPRE tramite private invece che tramite public o protected (l'inverso di quanto ti avevo detto ieri :D) ....... e ovviamente usare un mutex per l'accesso a quelle variabili se il programma è multithreaded :p
Le clausole "friend" valgono solo ed esclusivamente per le classi indicate in maniera esplicita, o consentono l'accesso anche alle classi derivate da quelle specificate?
La prima che hai detto. In parte è valida anche la seconda che hai detto ma solo indirettamente, cioè una classe derivata può accedere a quelle variabili eventualmente sfruttando i metodi ereditati dalla classe che è stata dichiarata friend (qualora ce ne fossero di adatti e raggiungibili)
Pensavo ad un meccanismo di questo tipo per creare un compromesso tra l'uso di funzioni setter e l'accesso diretto ai membri dell'oggetto: per le classi a cui è concesso, l'accesso è diretto, mentre per le altre è possibile solo mediante "setAttributo(...)".
Direi che la friend serve proprio a questo: creare "corsie preferenziali" tra classi STRETTAMENTE correlate dal punto di vista logico. Ma non bisogna abusarne altrimenti addio OOP. Ed è proprio per evitare abusi che la caratteristica di "amicizia concessa" da una classe ad altre classi (o funzioni, perchè si possono dichiarare friend anche singole funzioni o metodi) non è direttamente ereditabile dalle sue derivate
Sempre per chiacchierare, nei moduli del kernel com'è implementata (se c'è)?
Ti riferisci all'overloading? beh, intanto per limitarne la necessità si fa un uso intenso di argomenti "void *" e conseguente casting, dopo di che nei pochi casi che rimangono si usa il classico metodo di "più funzioni con nome differenziato dal tipo di argomento accettato" (es: pci_read_config_byte(), pci_read_config_word(), ..., per leggere l'area conf delle periferiche PCI, oppure inb(), inw(), outb(), outw(), ... , per accedere alle porte I/O del processore) oppure della funzione "comando generico" di cui uno degli argomenti passati è un valore numerico (di solito un int costante definito tramite macro del pre-processore) indicante il "sub-comando" da eseguire. Per esempio la ioctl è la regina di questo tipo di "overloading" oltre che la syscall più polimorfica (in senso OO) che un kernel unix-like abbia
E l'ereditarietà?
Semplicemente includendo struct (facenti le veci della classe base) in un'altra struct (che fa le veci della classe derivata). Tanto per rimanere in un esempio che abbiamo già usato, la struct file ha tra i suoi componenti anche un "struct inode *" rappresentante appunto (il puntatore a) un i-node che, nel contesto di un tipico file-system unix, può essere definito come un oggetto più generico del concetto di "file". Le funzioni che usano un'istanza della struct file possono accedere i dati della rispettiva struct inode ed anche usarne i "metodi" (definiti dall'apposita struct inode_operations di cui un puntatore è parte integrante della struct inode) tramite un apposito "back-pointer" presente nella struct file, cioè una delle variabili presenti nella struct file è un "struct inode *" che punta all'istanza di struct inode allocata per quel file
Ah, se per questo nemmeno io: si può discutere sulle alternative [...] ma non si può certo dire "è sbagliato, punto e basta". Caso mai, mi lascia perplesso il fatto che a volte Torvalds abbia atteggiamenti proprio di questo tipo, e si lasci andare a sentenze poco "tecniche" (tipo "C++ sucks"), perchè personalmente da un programmatore esperto e capace quale a saputo dimostrarsi mi aspetterei un atteggiamento, come dire, più professionale e più possibilista, una maggiore propensione all'innovazione, dato che il suo giudizio fondamentalmente è rimasto fermo agli esiti del primo, fallimentare approccio al C++ (colpa dei compilatori del periodo principalmente).
Perfettamente d'accordo
cdimauro
24-10-2004, 14:17
Originariamente inviato da asbuni
Senz'altro si (cioè, non mi sono mai addentrato a studiare i sorgenti del gcc ma credo che faccia così, io almeno farei così se dovessi fare un compilatore C++)
Non li ho studiati neppure io. Ricordo che le prime implementazioni OOP che ho visto (Il Turbo Pascal 5.5 :D) memorizzavano i puntatori ai metodi all'interno della struttura stessa della classe, ma ad offset negativi (es. alla word di offset -2 ci stava il primo metodo virtuale, a -4 il secondo, ecc.).
Non sarebbe neppure male pensare di piazzarle in mezzo alla struttura dei dati, alternando agli attributi gli indirizzi dei metodi virtuali. ;)
L'unica differenza è che la "vtable" (nota le virgolette) fatta a mano dal programmatore in C è espressamente alterabile programmaticamente in run-time mentre la vtable (senza virgolette) generata compile-time dal C++ no. O mi sbaglio?
Non ti sbagli. Normalmente. Perché, conoscendo il compilatore, si potrebbe sempre cercare di "giocare sporco" in qualche modo... :D
forse ho capito cosa intendi: probabilmente ho usato il termine "polimorfismo" in modo improprio per questo caso: si tratta più esattamente di uno di quei "metamorfismi" a cui mi riferivo nel mio primo post. Se era questo che ti era "sfuggito" è colpa mia. Pardon
OK, adesso è più chiaro. :)
Si, sul modo sono d'accordo con te. Poteva starsene zitto oppure, per restare "estremista", dire semplicemente "il C++ mi sta antipatico punto" e avrebbe fatto una figura migliore. Oppure avrebbe potuto semplicemente usare una scusa che nessuno in onestà potrebbe controbattere: "ormai abbiamo iniziato così e per ora non abbiamo tempo per iniziare la conversione" cosa che è probabilmente vera a prescindere dalla possibile antipatia o ignoranza nei confronti del linguaggio
Non posso che quotare ogni singola parola... ;)
cdimauro
24-10-2004, 14:25
Ho sentito spesso l'esigenza di un'operazione di shift aritmetico a destra (oltre a quello "classico" già citato), che mi restituisse 0 nel caso che l'input fosse -1, in modo da "emulare" a tutti gli effetti una divisione intera per 2. Non capisco perché la maggior parte delle ISA preferisce duplicare lo shift logico a sinistra e lo shift aritmetico a sinistra, facendogli fare le stesse operazioni... :muro:
Originariamente inviato da asbuni
Ti riferisci all'overloading? beh, intanto per limitarne la necessità si fa un uso intenso [...]
Ok, capito :)
Originariamente inviato da cdimauro
Ho sentito spesso l'esigenza di un'operazione di shift aritmetico a destra (oltre a quello "classico" già citato), che mi restituisse 0 nel caso che l'input fosse -1, in modo da "emulare" a tutti gli effetti una divisione intera per 2. Non capisco perché la maggior parte delle ISA preferisce duplicare lo shift logico a sinistra e lo shift aritmetico a sinistra, facendogli fare le stesse operazioni...
Già. Forse considerano la moltiplicazione tra interi sufficientemente veloce, magari con qualche "trucco" sulle latenze delle porte (ovvero dei transistor in cascata) riescono ad eguagliare lo shift sequenziale per potenze di due sufficientemente basse da rendere (nell'ottica dei progettisti) il doppio shift a sinistra uno "sforzo" (nella progettazione e nell'implementazione(come numero di transistor)) evitabile; oppure potrebbero aver giudicato quei pochi (in fondo) transistor risparmiabili considerando lo shift aritmetico a sinistra meno frequente, rispetto a quello logico, di quanto non accada con lo shift a destra (per via della comodità di fare divisioni per due rapidamente negli algoritmi con strategia divide et impera: almeno questo shift aritmetico deve esserci, o dovrebbe...). Comunque è una limitazione in effetti. Una curiosità: i 68000 come lo implementano?
cdimauro
25-10-2004, 05:00
Allo stesso modo. :muro:
Comunque, penso che l'unica motivazione stia nella "simmetria" dell'ALU, che adottando una soluzione come quella da me pensata, si troverebbe "sbilanciata" (5 operazioni di shift/rotazione a destra, e soltanto 3 a sinistra).
In realtà basterebbe un comparatore (per vedere se ci troviamo nel caso citato) e un multiplexer per scegliere il risultato finale (fra i due possibili): una manciata di transistor, insomma. :rolleyes: :muro:
repne scasb
25-10-2004, 13:24
Originariamente inviato da cdimauro
Non li ho studiati neppure io. Ricordo che le prime implementazioni OOP che ho visto (Il Turbo Pascal 5.5 :D) memorizzavano i puntatori ai metodi all'interno della struttura stessa della classe, ma ad offset negativi (es. alla word di offset -2 ci stava il primo metodo virtuale, a -4 il secondo, ecc.).
Non sarebbe neppure male pensare di piazzarle in mezzo alla struttura dei dati, alternando agli attributi gli indirizzi dei metodi virtuali. ;)
Un compilatore C++ di solito (leggi sempre) memorizza all'interno della struttura dell'oggetto un puntatore alla vtable (una per classe) che non puo' essere modificata a runtime, come dice giustamente asbuni.
Questo per una classe a derivazione singola, per la derivazioni multipla le regole si complicano parecchio, e non le ricordo certo a memoria :p, e ogni compilatore spesso fa quello che gli pare.
Per chi e' curioso di saperne di piu' su come un compilare C++ di solito implementa i costrutti (anche per capire quanto affermare che il C++ sia intrinsecamente piu' lento del C e' una bagginata), questo libro e' piu' che ottimo:
http://www.amazon.co.uk/exec/obidos/ASIN/0201834545/qid=1098708958/ref=sr_8_xs_ap_i1_xgl/202-4027200-9583865
Originariamente inviato da cdimauro
Comunque, penso che l'unica motivazione stia nella "simmetria" dell'ALU, che adottando una soluzione come quella da me pensata, si troverebbe "sbilanciata" (5 operazioni di shift/rotazione a destra, e soltanto 3 a sinistra).
Qui mi sono perso: ti spiace elencarmi queste operazioni? Thanks
Originariamente inviato da repne scasb
Allora, per MC680x0 c'e' sia lo shift aritmetico che logico, gli opcode sono diversi (il bit 9 e' settato a 1 per shift logico in memoria, 0 per aritmetico - il bit 3 e' settato a 1 per shift logico immediato, 0 per aritmetico), le equivalenti istruzioni sono ASL, ASR (aritmetico), LSL, LSR (logico).
Veniamo alle differenze: A parte le differenze sulla consevazione o meno del bit di segno, si hanno le seguenti diversita' sui flags: [...]
Praticamente hai confermato (ulteriormente) i miei sospetti che il processorino "didattico" con cui ho avuto "a che fare" si ispiri molto(-issimo) ai Motorola, fatta eccezione, ovviamente, per gli opcode (comunque un set limitato rispetto ai processori reali, e con un formato istruzione diverso) e per l'indirizzamento dei byte (adotta la stessa convenzione della cpu 8086, ovvero il byte meno significativo è quello con indirizzo di memoria più basso). Gli shift previsti, guarda caso, hanno gli stessi nomi (con l'aggiunta della lunghezza del dato: B(yte), W(ord), L(ongword)). Altri caratteri "salienti": formato istruzione fisso a 32 bit, pari dimensione degli 8 registri interni (tutti destinati indifferentemente a dati e indirizzi, quindi 8 in meno rispetto al 68000 (non ci sono i registri riservati all'indirizzamento)), interconnessione interna a bus (singolo: 1 solo accesso, in lettura o scrittura, ad un solo registro alla volta; nei 68000 il bus era multiplo o sbaglio? Negli 8086, se non ricordo male, l'interconnessione usa multiplexer, giusto?), stesse convenzioni per l'assembly (operazioni a due operandi (tranne le ovvie eccezioni a 1 operando), mnemonici del tipo Op<sorg><dest>, parentesi tonde per l'indirizzamento indiretto). Una curiosità sulle rotazioni (RCR, ROR, RCL, ROL: a destra e sinistra, con e senza carry (cioè con il bit uscente al carry e il bit del carry in ingresso all'altro "estremo" o con bit uscente riportato subito in ingresso e al carry)): su questa ISA settano tutte i flag praticamente come gli shift logici (N=0, Z=0, V=0, carry e parity alterati), quindi, volendole usare per eseguire uno shift "rapido" su interi a 64 bit (shift logico sulla longword meno significativa, rotazione con carry sulla più significativa), ottengo uno shift logico; l'unica alternativa che, da "profano" della programmazione in assembly, mi viene in mente (per questo caso specifico) è valutare il riporto sul primo shift e quindi fare uno shift aritmetico sulla longword più significativa, eventualmente sommando 1 (qualcosa tipo:
...
LSRL R1 ;o LSLL R1
JC LABEL1 ;
ASRL R2 ;o ASL R2
LABEL1: ASRL R2 ;o ASLL R2
ADDL #1h R2; )
In genere come si fa?
cdimauro
26-10-2004, 08:13
ADD.L R1,R1 ; "Shifta" a sinistra di un posto.
ADDX.L R2,R2 ; Come sopra, ma aggiunge il carry della precedente operazione. ;)
Per il resto, t'ha già risposto repne scasb. ;)
cdimauro
26-10-2004, 08:14
Originariamente inviato da fek
Un compilatore C++ di solito (leggi sempre) memorizza all'interno della struttura dell'oggetto [B]un puntatore alla vtable[B] (una per classe) che non puo' essere modificata a runtime, come dice giustamente asbuni.
Hai perfettamente ragione: chissà a cosa pensavo... :rolleyes: :muro:
repne scasb
26-10-2004, 10:22
cdimauro
26-10-2004, 13:02
Direi di sì. :)
Peccato che non sia stato chiaro anche a chi ha progettato le ISA dei vari processori, perché sarebbe stato comodo sostituire a tutti gli effetti le divisioni per potenze di due con gli shift aritmetici "corretti". Sob. :(
Originariamente inviato da repne scasb
Sono dura di comprendonio [...]
Più che altro, ho confuso io il discorso ragionando su un set di istruzioni ridotto (assumendo implicitamente che R1 ed R2 avessero contenuto invertito nel caso dello shift destro o sinistro, diciamo che l'assembly non è il mio pane quotidiano :D). All'add con carry ho pensato "qualche" ora dopo aver postato... Assumendo che il carry venga settato con il bit "uscente" immutato, nel caso di uno shift aritmetico a sinistra (globale) posso sostituire il primo add con uno shift logico, giusto? Il secondo add (con carry) sui 32 bit più significativi dovrebbe settare i flag opportunamente.
Per lo shift aritmetico a destra sostituire LSR con ASR
Giusto, ho capito. Grazie a entrambi :)
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.