View Full Version : Array e dimensione MAX
mister bianchi
02-09-2014, 16:42
Ciao a tutti,
sto imparando le basi del C, ho fatto questo piccolo programmino per imparare gli array.
#include<stdio.h>
int main()
{
int intero[5], i;
for (i=0; i<10; i++)
{
intero[i]=i;
}
for (i=0; i<10; i++)
{
printf("intero[i]: %d\n", intero[i]);
}
return 0;
}
Come mai anche se ho dichiarato l'array con dimensione 5 riesco comunque a scrivere oltre questa dimensione? Non dovrei ottenere un errore?
Silvia2003
02-09-2014, 16:58
"Crocedelizia" del c/c++... lui te lo lascia fare ma il risultato è imprevedibile (probabile crash completo!).
Se vuoi che il compilatore si "accorga" dell'errore cambia linguaggio.
mister bianchi
02-09-2014, 17:04
Non voglio cambiare linguaggio (almeno per ora! :D), voglio capire perchè me lo fa fare senza darmi nè errori nè warnings. In pratica cosa succede? Vado a scrivere fuori dall'array? Eseguendo il programma tutto fila liscio senza crash come se avessi realmente dichiarato un array di 10 elementi.
Silvia2003
02-09-2014, 17:20
Non voglio cambiare linguaggio
e allora bisogna accettarlo per com'è, apprezzandone le caratteristiche (è parte della "potenza" di C e C++) e studiandolo per evitare di incappare in errori.
Te lo fa fare semplicemente perché non è previsto alcun controllo. Il fatto che il programma possa girare senza produrre alcun errore è del tutto casuale (e insidioso in fase di debug).
mister bianchi
02-09-2014, 17:23
(e insidioso in fase di debug).
Cioè? Scusa ma sono un neofita...
Silvia2003
02-09-2014, 17:52
Significa che quando il tuo programma andrà in errore (e scrivendo oltre i limiti del dimensionamento dell'array primo o poi accadrà) sarà più ostico trovare l'origine di tale errore: l'insidia sta proprio nel fatto che eseguendo più volte il programma non sempre si ottiene l'errore (è un errore ottenibile a run-time).
Fai qualche test ad esempio dichiarando qualche altro array o scrivendo qualche elemento in più dei 5 aggiuntivi proposti nell'esempio...
Silvia2003
02-09-2014, 17:55
Prova questo esempio...
#include <stdio.h>
int main()
{
int ar1[5], ar2[10], i;
for(i = 0; i < 20; ++i) {ar1[i] = i;}
for(i = 0; i < 10; ++i) {ar2[i] = i;}
for(i = 0; i < 10; ++i) {
printf("%d - %d\n",ar1[i], ar2[i]);
}
return 0;
}
mister bianchi
02-09-2014, 18:11
Ottengo questo
0 - 0
1 - 1
2 - 2
3 - 3
4 - 4
5 - 5
6 - 6
7 - 7
8 - 8
9 - 9
Errore di segmentazione
Silvia2003
02-09-2014, 18:26
si è solo "stressato" maggiormente l'esempio (aggiunto un array, scritto qualche elemento in più...) e, improvvisamente, ciò che prima sembrava funzionare ora non funziona più.
In un esempio così semplice è molto semplice capire l'origine del problema ma, prova a pensare cosa accadrebbe in codice molto più corposo e frazionato.
Silvia2003
02-09-2014, 21:00
Io ho compilato con GCC abilitando tutti i possibili warnings senza alcun avviso.
Non ho provato con VC.
Silvia2003
02-09-2014, 21:22
... anche VC 2013 compila senza problemi
Impostazioni compilatore C/C++
Livello avvisi : Abilita tutti gli avvisi (/Wall)
Considera gli avvisi come errori : Si (/WX)
In ogni caso, a prescindere dal possibile specifico supporto eventualmente offerto dal compilatore (quale?) rimane il fatto che il linguaggio consente di farlo: C e C++ lasciano al programmatore la responsabilità di gestire questo e altri aspetti (qualsiasi testo di base utilizzato per apprendere il C o il C++ credo ponga in buona evidenza questa caratteristica).
wingman87
02-09-2014, 21:30
Prova questo esempio...
#include <stdio.h>
int main()
{
int ar1[5], ar2[10], i;
for(i = 0; i < 20; ++i) {ar1[i] = i;}
for(i = 0; i < 10; ++i) {ar2[i] = i;}
for(i = 0; i < 10; ++i) {
printf("%d - %d\n",ar1[i], ar2[i]);
}
return 0;
}
Una curiosità: ultimamente vedo spesso l'incremento di i nei cicli utilizzando un preincremento invece di un postincremento... c'è un motivo o è solo cambiata la moda? Immagino la seconda ma sono curioso anche dell'origine di questa moda.
Silvia2003
02-09-2014, 22:04
Non è un moda ma una questione di performance.
il pre-incremento (++i) equivale a:
i = i +1
il post-incremento (i++) equivale a:
tmp = i; //(necessità di conservare il valore prima dell'incremento = valore ritornato)
i = i + 1;
wingman87
02-09-2014, 22:31
Interessante, grazie.
sottovento
03-09-2014, 06:32
Non è un moda ma una questione di performance.
il pre-incremento (++i) equivale a:
i = i +1
il post-incremento (i++) equivale a:
tmp = i; //(necessità di conservare il valore prima dell'incremento = valore ritornato)
i = i + 1;
A dire il vero ci sono molti processori che hanno nativamente sia istruzioni di post sia di pre incremento/decremento, e il post incremento risulta decisamente piu' performante.
Quanto scrivi e' vero soprattutto quando si devono manipolare oggetti, in quel caso occorre sicuramente ricorrere implicitamente ad una variabile temporanea
Silvia2003
03-09-2014, 07:33
Può essere ma si tratta ugualmente di good-practice. I compilatori eseguono comunque una serie di ottimizzazioni più o meno "aggressive" ma una buona codifica iniziale aiuta sempre.
Premesso che la differenza in termini di tempo di esecuzione tra pre-incremento e post-incremento è praticamente trascurabile (almeno nel caso dell'esempio proposto), preferisco ricorrere ad una soluzione piuttosto che l'altra in base alla "logica" necessità.
Ciao a tutti,
sto imparando le basi del C, ho fatto questo piccolo programmino per imparare gli array.
#include<stdio.h>
int main()
{
int intero[5], i;
for (i=0; i<10; i++)
{
intero[i]=i;
}
for (i=0; i<10; i++)
{
printf("intero[i]: %d\n", intero[i]);
}
return 0;
}
Come mai anche se ho dichiarato l'array con dimensione 5 riesco comunque a scrivere oltre questa dimensione? Non dovrei ottenere un errore?
Accedere a locazioni di memoria contenute oltre il limite espresso al momento della dichiarazione dell'array è undefined behaviour in C .
In altre parole il linguaggio non asserisce nulla di definitivo e specifico sull'argomento e il compilatore non è tenuto a fare o dire nulla; si suppone che tu in qualità di programmatore C semplicemente non dovresti mai fare nulla di simile .
Silvia2003
03-09-2014, 08:34
... tu in qualità di programmatore C semplicemente non dovresti mai fare nulla di simile ...
L'uso del condizionale sintetizza pienamente il concetto!
Ciao a tutti,
sto imparando le basi del C, ho fatto questo piccolo programmino per imparare gli array.
#include<stdio.h>
int main()
{
int intero[5], i;
for (i=0; i<10; i++)
{
intero[i]=i;
}
for (i=0; i<10; i++)
{
printf("intero[i]: %d\n", intero[i]);
}
return 0;
}
Come mai anche se ho dichiarato l'array con dimensione 5 riesco comunque a scrivere oltre questa dimensione? Non dovrei ottenere un errore?
Non c'è modo di sapere che è sbagliato prima di eseguire il codice. Come fa il compilatore a sapere che i sarà maggiore di 5 ? Deve interpretare il significato del comando for e delle eventuali formule. Infatti nel mezzo ci potrebbe essere scritto
i=i-3
anche questa formula dovrebbe essere valutata. In altre parole il codice dovrebbe essere eseguito.
Ma quando lo esegui non sai cosa c'è al di là della quinta locazione di memoria, magari c'è qualcosa che in quel frangente non serve più, per cui anche sovrascrivendola non si hanno errori.
Però non c'è garanzia.
Diverso è se scrivi i[7]=2; allora il compilatore sa che l'array è al massimo di 5 ma tu stai scrivendo nella 7ma locazione
Silvia2003
06-09-2014, 08:41
Non credo, prova questo
int main()
{
int i[5];
i[7] = 500;
printf("%d", i[7]);
return 0;
}
... compilazione pulita e senza alcun warning (GCC)
x86_64-w64-mingw32-gcc.exe -Wall -pedantic -Wextra -g -Winline -Wunreachable-code -pedantic-errors -pedantic -g -c A:\prova1\main.c -o obj\Debug\main.o
x86_64-w64-mingw32-g++.exe -o bin\Debug\prova1.exe obj\Debug\main.o
Output file is bin\Debug\prova1.exe with size 125.40 KB
Process terminated with status 0 (0 minute(s), 0 second(s))
0 error(s), 0 warning(s) (0 minute(s), 0 second(s))
credo di avere abilitato tutti i flag relativi ai possibili warning.
Il compilatore semplicemente non esegue alcun controllo degli indici poiché, come già stato ben evidenziato, in C (ma anche in C++) ciò non è previsto.
credo di avere abilitato tutti i flag relativi ai possibili warning.
Il compilatore semplicemente non esegue alcun controllo degli indici poiché, come già stato ben evidenziato, in C (ma anche in C++) ciò non è previsto.
Non è impossibile fare quel tipo di controlli. Specialmente per codice come quello che hai riportato nell'esempio in cui il numero di elementi è esplicito. Dipende tutto dal compilatore. In llvm per esempio è un controllo abilitato di default. Se provo a compilare ottengo due warning nelle linee di codice in cui si cerca di accedere oltre il limite.
asgard:~ % clang array.c
array.c:6:5: warning: array index 7 is past the end of the array (which contains 5 elements)
[-Warray-bounds]
i[7] = 500;
^ ~
array.c:5:5: note: array 'i' declared here
int i[5];
^
array.c:7:18: warning: array index 7 is past the end of the array (which contains 5 elements)
[-Warray-bounds]
printf("%d", i[7]);
^ ~
array.c:5:5: note: array 'i' declared here
int i[5];
^
2 warnings generated.
Silvia2003
06-09-2014, 19:01
Interessante. Normalmente utilizzo VC e/o GCC e con con questi due nessuna segnalazione (ovviamente senza ricorrere a tools esterni).
Prima o poi proverò anche a LLVM...
int main()
{
int i[5];
i[7] = 500;
printf("%d", i[7]);
return 0;
}
Evidentemente GCC non ha questa funzione, non dipende dal linguaggio ma dal compilatore. Ad ogni modo in questo caso è tecnicamente possibile sapere che c'è un errore già in fase di compilazione, perchè gli indici sono numeri e non variabili.
Il compilatore semplicemente non esegue alcun controllo degli indici poiché, come già stato ben evidenziato, in C (ma anche in C++) ciò non è previsto.
Ma scusa, nel caso
intero[i]=i;
con quale linguaggio ti da un avviso del genere in fase di compilazione ? Io credo nessuno.
Al massimo potrai avere un errore del debugger dopo che il programma viene lanciato e raggiunge quel preciso punto. Oppure nei linguaggi interpretati ma sempre a runtime.
E comunque non so quanti debugger riescano a isolare un errore del genere.
Silvia2003
08-09-2014, 08:27
Evidentemente GCC non ha questa funzione, non dipende dal linguaggio ma dal compilatore.
Come evidenziato non solo GCC ma anche VC. In ogni caso trattasi evidentemente di una feature del compilatore... utile ma bisogna esserne consapevoli
AnonimoVeneziano
09-09-2014, 02:34
Non c'è modo di sapere che è sbagliato prima di eseguire il codice. Come fa il compilatore a sapere che i sarà maggiore di 5 ? Deve interpretare il significato del comando for e delle eventuali formule. Infatti nel mezzo ci potrebbe essere scritto
anche questa formula dovrebbe essere valutata. In altre parole il codice dovrebbe essere eseguito.
Ma quando lo esegui non sai cosa c'è al di là della quinta locazione di memoria, magari c'è qualcosa che in quel frangente non serve più, per cui anche sovrascrivendola non si hanno errori.
Però non c'è garanzia.
Diverso è se scrivi i[7]=2; allora il compilatore sa che l'array è al massimo di 5 ma tu stai scrivendo nella 7ma locazione
Non e' impossibile.
Facendo analisi statica del programma sarebbe possibile in questo esempio vedere facilmente una scrittura out-of-bounds.
CLANG e' in grado di fare questo tipo di analisi statica e infatti butta fuori un warning in quel caso.
E' anche possibile usare CLANG per fare una analisi statica del proprio programma più' approfondita e trovare possibili problemi prima di compilare.
CLANG static analyzer per piu' info: http://clang-analyzer.llvm.org
Se poi tutto fallisce e' possibile usare tool a runtime (valgrind / clang address-sanitizer), ma di recente e' possibile ottenere molte più'informazioni semplicemente in maniera statica :)
Non è un moda ma una questione di performance.
il pre-incremento (++i) equivale a:
i = i +1
il post-incremento (i++) equivale a:
tmp = i; //(necessità di conservare il valore prima dell'incremento = valore ritornato)
i = i + 1;
Dubito fortemente che un compilatore moderno con le ottimizzazioni abilitate traduca il codice come dici te, specialmente se il valore dell'espressione non viene usato.
La best practice sta nello scrivere il codice pensando ad un livello più alto e far fare al compilatore il suo mestiere.
Se poi si dovesse scoprire che quello è il collo di bottiglia della tua applicazione (cosa improbabile) allora FORSE vai a vedere l'assembly generato.
Silvia2003
09-09-2014, 18:35
Dubito fortemente che un compilatore moderno con le ottimizzazioni abilitate traduca il codice come dici te,
Lo voglio sperare... è stato chiesto perché utilizzare il pre-incremento in luogo del post-incremento e, a livello logico, questa è la spiegazione.
Se ne hai un'altra, migliore, contribuisci pure... salvo tu non sia realmente convinto che trattasi unicamente di seguire una "moda"
wingman87
09-09-2014, 23:31
E' una moda come era una moda scrivere i++ invece di i=i+1. Questo perché il guadagno è minimo se non nullo per via delle ottimizzazioni. La spiegazione è interessante ma condivido il pensiero di Warduck:
La best practice sta nello scrivere il codice pensando ad un livello più alto e far fare al compilatore il suo mestiere.
Lo voglio sperare... è stato chiesto perché utilizzare il pre-incremento in luogo del post-incremento e, a livello logico, questa è la spiegazione.
Se ne hai un'altra, migliore, contribuisci pure... salvo tu non sia realmente convinto che trattasi unicamente di seguire una "moda"
La variabile temporanea viene effettivamente creata usando il post incremento. Se guardi nell'assembly generato dal compilatore è facile trovare la mov usata per copiare il vecchio valore in un altro registro. Questo però è codice inutile ed i compilatori sono diventati bravissimi a trovare questi pezzi di codice morto. Anche usando il livello più basso di ottimizzazione la variabile temporanea viene cancellata e post/pre-incremento diventano identici in questo caso.
Comunque con i processori di oggi che sono capaci di eseguire più istruzioni per ogni ciclo di clock ed in grado di riordinarle al volo se gli fa comodo, preoccuparsi di un migliaio di mov inutili tra i registri non ha più senso. Come ha detto WarDuck. Lasciate fare al compilatore il suo lavoro.
sottovento
10-09-2014, 06:33
Il post-incremento, cosi' come il post-decremento equivale piu' o meno a quanto ha detto Silvia SOLO nel caso si stia utilizzando il C++, non il C.
In quel caso, infatti, la variabile temporanea risulta necessaria per salvare e ritornare l'OGGETTO corrente ed avere il tempo per passare al successivo.
Con i tipi di dati primari, come dicevo questo NON avviene, soprattutto perche', come si diceva, gli incrementi pre/post vengono tradotti direttamente nella corrispondente istruzione assembler.
Sebbene il C sembri un linguaggio ad alto livello, occorre tenere presente che in realta' e' poco piu' di un assembler strutturato, ed in molti sistemi a microprocessore e' esattamente usato in questo modo e non si puo' prescindere da questo utilizzo!
Nel consegue che le istruzioni
i++;
i+=1;
++i;
NON sono la stessa cosa.
Quello che dice WarDuck e' verissimo e sacrosanto, e fa parte dei principi della buona programmazione; tuttavia ci sono - esistono ancora ed esisteranno ancora per molto tempo! - parecchi sistemi in cui si usa il C proprio come sostituzione dell'assembler, e queste cose contano, cosi' come conta la differenza di prestazioni fra il pre ed il post incremento.
Silvia2003
10-09-2014, 07:57
Io continuo a sostenere che (come mi è stato insegnato) è meglio usare il post-incremento solo quando è necessario.
A prescindere da chi deve fare determinati "mestieri", usare la testa non fa mai male.
sottovento
10-09-2014, 09:49
Io continuo a sostenere che (come mi è stato insegnato) è meglio usare il post-incremento solo quando è necessario.
A prescindere da chi deve fare determinati "mestieri", usare la testa non fa mai male.
Sono confuso: usare le testa o fare come e' stato insegnato? A quale delle due frasi devo credere?
Come si diceva, se in C++ definisci una lista di oggetti e poi la scandisci, SICURAMENTE il pre-incremento da performance maggiori, visto che non devi chiamare costruttori di copia e tutto il resto. Poi il professore immagino si sia fermato qui.
Per quanto riguarda il resto: occorre prendersi in mano il manuale del processore e leggerlo, non ci sono alternative. Poi puoi continuare a sostenere quello che vuoi
Sono confuso: usare le testa o fare come e' stato insegnato? A quale delle due frasi devo credere?
Come si diceva, se in C++ definisci una lista di oggetti e poi la scandisci, SICURAMENTE il pre-incremento da performance maggiori, visto che non devi chiamare costruttori di copia e tutto il resto. Poi il professore immagino si sia fermato qui.
Per quanto riguarda il resto: occorre prendersi in mano il manuale del processore e leggerlo, non ci sono alternative. Poi puoi continuare a sostenere quello che vuoi
Considerando che in molti compilatori C++ si usano tecniche di elision per la copia e via discorrendo, lascia tutto il tempo che trova.
Per dire nella copia di una std::string si può adottare una tecnica di copy on write.
Ci sono cose più costose di ordini di grandezza (allocazione ad esempio) ed in ogni caso quando il problema sono le performance si dovrebbe ricorrere ad un profiler perché ogni applicazione fa storia a se.
AnonimoVeneziano
10-09-2014, 11:07
La variabile temporanea viene effettivamente creata usando il post incremento. Se guardi nell'assembly generato dal compilatore è facile trovare la mov usata per copiare il vecchio valore in un altro registro. Questo però è codice inutile ed i compilatori sono diventati bravissimi a trovare questi pezzi di codice morto. Anche usando il livello più basso di ottimizzazione la variabile temporanea viene cancellata e post/pre-incremento diventano identici in questo caso.
Comunque con i processori di oggi che sono capaci di eseguire più istruzioni per ogni ciclo di clock ed in grado di riordinarle al volo se gli fa comodo, preoccuparsi di un migliaio di mov inutili tra i registri non ha più senso. Come ha detto WarDuck. Lasciate fare al compilatore il suo lavoro.
Non sulla mia configurazione:
int main() {
int i = 0;
volatile int c = 0;
for (; i != 100; ++i) {
c = i;
}
return 0;
}
e
int main() {
int i = 0;
volatile int c = 0;
for (; i != 100; i++) {
c = i;
}
return 0;
}
generano esattamente lo stesso codice usando clang circa SVN 3.3.
Output come reference:
.section __TEXT,__text,regular,pure_instructions
.globl _main
.align 4, 0x90
_main: ## @main
.cfi_startproc
## BB#0:
pushq %rbp
Ltmp2:
.cfi_def_cfa_offset 16
Ltmp3:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp4:
.cfi_def_cfa_register %rbp
movl $0, -4(%rbp)
xorl %eax, %eax
.align 4, 0x90
LBB0_1: ## =>This Inner Loop Header: Depth=1
movl %eax, -4(%rbp)
incl %eax
cmpl $100, %eax
jne LBB0_1
## BB#2:
xorl %eax, %eax
popq %rbp
ret
.cfi_endproc
.subsections_via_symbols
AnonimoVeneziano
10-09-2014, 11:13
Comunque vorrei far notare che state tutti dicendo la stessa cosa :D
Silvia usa il pre-incremento, perche' lo ritiene più performante e tutti state a dirgli che i++ genera una copia non necessaria, che e' esattamente la stessa cosa che sta dicendo lei ... quindi non ho capito perché state discutendo :D
In ogni caso il salvataggio della variabile di incremento in qualsiasi compilatore ottimizzante se non viene utilizzata verra' eliminato.
Questo in C, C++, Fortran o whatever-language. Se non lo fa il compilatore e' performance-buggato :)
Non sulla mia configurazione:
Io devo avere la versione dopo (3.4svn), anche se è roba Apple quindi va a capire dove hanno messo le mani. Comunque ho provato ora e ottengo due versioni che differiscono proprio per una mov prima della add 1.
mov eax, dword ptr [rbp - 20]
mov ecx, eax
add ecx, 1
v.
mov ecx, dword ptr [rbp - 20]
add ecx, 1
Se da -O0 passo a -O1 diventano identici.
...
In ogni caso il salvataggio della variabile di incremento in qualsiasi compilatore ottimizzante se non viene utilizzata verra' eliminato.
Questo in C, C++, Fortran o whatever-language. Se non lo fa il compilatore e' performance-buggato :)
Io ho risposto perché mi pareva di capire che ci fosse la convinzione che anche con i compilatori di oggi c'è effettivamente un guadagno di prestazioni usando il pre-incremento.
Silvia2003
10-09-2014, 14:13
Sono confuso: usare le testa o fare come e' stato insegnato? A quale delle due frasi devo credere?
Semplicemente mi è stato insegnato ad utilizzare la testa e andare per logica.
In un esempio come quello proposto le performance del pre-incremento Vs. post-incremento possono essere solo migliori o uguali e allora:
- Se non mi serve il valore prima dell'incremento allora utilizzo il pre-incremento (a prescindere da possibili/eventuali ottimizzazioni che non governo esplicitamente)
- Se mi serve il valore prima dell'incremento allora utilizzo il post-incremento
Se mi viene insegnata una cosa che ritengo logica (eseguire tendenzialmente il minor numero di operazioni possibili) non vedo motivi per non applicarla.
Poi ognuno creda e/o faccia ciò che vuole.
AnonimoVeneziano
10-09-2014, 14:54
Io devo avere la versione dopo (3.4svn), anche se è roba Apple quindi va a capire dove hanno messo le mani. Comunque ho provato ora e ottengo due versioni che differiscono proprio per una mov prima della add 1.
mov eax, dword ptr [rbp - 20]
mov ecx, eax
add ecx, 1
v.
mov ecx, dword ptr [rbp - 20]
add ecx, 1
Se da -O0 passo a -O1 diventano identici.
Beh, con -O0 mi aspettavo che l'istruzione morta non venisse eliminata, visto che O0 disabilita tutte le ottimizzazioni (e' solo per debugging).
In uno scenario vero (ossia con ottimizzazioni abilitate) le due espressioni risultano identiche :)
Io ho risposto perché mi pareva di capire che ci fosse la convinzione che anche con i compilatori di oggi c'è effettivamente un guadagno di prestazioni usando il pre-incremento.
In realta' qua mi pare di capire che non ci si sta capendo :sofico:
sottovento
10-09-2014, 19:09
...
generano esattamente lo stesso codice usando clang circa SVN 3.3.
...
Interessante. Questo dimostra che il tuo processore non ha istruzioni per il pre/post incremento, altrimenti il codice generato non sarebbe evidentemente lo stesso, ma al massimo dell'ottimizzazione avrebbe utilizzato queste istruzioni, giusto?
Oppure il processore ce le ha ma il compilatore non e' in grado di utilizzarle.
Occorrerebbe ripetere l'esperimento con un'accoppiata processore che abbia istruzioni pre/post incremento + compilatore che sappia usarle...
sottovento
10-09-2014, 19:18
Comunque vorrei far notare che state tutti dicendo la stessa cosa :D
Silvia usa il pre-incremento, perche' lo ritiene più performante e tutti state a dirgli che i++ genera una copia non necessaria, che e' esattamente la stessa cosa che sta dicendo lei ... quindi non ho capito perché state discutendo :D
In ogni caso il salvataggio della variabile di incremento in qualsiasi compilatore ottimizzante se non viene utilizzata verra' eliminato.
Questo in C, C++, Fortran o whatever-language. Se non lo fa il compilatore e' performance-buggato :)
Veramente i termini della discussione erano leggermente diversi.
Se per esempio in C++ hai un puntatore ad oggetto e con questo scandisci una serie di oggetti, allora Silvia ha perfettamente ragione poiche' viene chiamato il costruttore di copia per riportare l'oggetto precedente, poi si passa al successivo e la procedura ha termine.
Io sostengo che esistono parecchi processori (alcuni anche targati MIL-, per intenderci, ma anche Motorola) che fra le loro istruzioni assembler hanno delle istruzioni di pre/post incremento/decremento. L'ambiente di sviluppo e' decisamente scarno e spesso e' ridotto ad un sottoinsieme del C.
Tuttavia il compilatore in dotazione e' in grado di utilizzare queste istruzioni. Guardando sui manuali, risulta chiaro che nella maggior parte delle volte l'istruzione di post incremento e' decisamente piu' performante.
La domanda e': vale la pena di fare queste ottimizzazioni? La risposta naturalmente dipende da quello che devi sviluppare e dalla capacita' di calcolo che hai a disposizione. Per questo motivo questa ottimizzazione viene ampiamente utilizzata e, insieme ad altri accorgimenti simili, riescono ad ottimizzare le prestazioni fino al 30%. Il 30% di ottimizzazione sul software permette di acquistare processori meno performanti ma piu' economici, ed e' un vantaggio se hai intenzione di vendere milioni di esemplari,no?
AnonimoVeneziano
10-09-2014, 19:23
Interessante. Questo dimostra che il tuo processore non ha istruzioni per il pre/post incremento, altrimenti il codice generato non sarebbe evidentemente lo stesso, ma al massimo dell'ottimizzazione avrebbe utilizzato queste istruzioni, giusto?
Oppure il processore ce le ha ma il compilatore non e' in grado di utilizzarle.
Occorrerebbe ripetere l'esperimento con un'accoppiata processore che abbia istruzioni pre/post incremento + compilatore che sappia usarle...
Non ho ben capito il discorso che fai sulle istruzioni di pre/post incremento ... puoi specificare meglio a cosa ti riferisci?
Il mio processore e' un normalissimo x86 (Haswell) di un MacBook Pro Retina.
In questo caso non ci sarebbe nulla da utilizzare in ogni caso. Il post-incremento e' diverso dal pre-incremento se e solo se il valore precedente all'incremento viene in qualche modo utilizzato, altrimenti ai fini del codice i due sono esattamente identici.
Il compilatore , giustamente, trasforma il tutto nella forma più efficiente , il pre-incremento infatti.
Non credo che anche se ci fossero state istruzioni speciali di qualsiasi tipo il risultato sarebbe cambiato, perché questo tipo di ottimizzazioni e' fatta dal compilatore ancora prima di sapere per che macchina si stia compilando (e' una ottimizzazione di middle-end chiamata DCE o Dead Code Elimination).
AnonimoVeneziano
10-09-2014, 19:32
Veramente i termini della discussione erano leggermente diversi.
Se per esempio in C++ hai un puntatore ad oggetto e con questo scandisci una serie di oggetti, allora Silvia ha perfettamente ragione poiche' viene chiamato il costruttore di copia per riportare l'oggetto precedente, poi si passa al successivo e la procedura ha termine.
Io sostengo che esistono parecchi processori (alcuni anche targati MIL-, per intenderci, ma anche Motorola) che fra le loro istruzioni assembler hanno delle istruzioni di pre/post incremento/decremento. L'ambiente di sviluppo e' decisamente scarno e spesso e' ridotto ad un sottoinsieme del C.
Tuttavia il compilatore in dotazione e' in grado di utilizzare queste istruzioni. Guardando sui manuali, risulta chiaro che nella maggior parte delle volte l'istruzione di post incremento e' decisamente piu' performante.
La domanda e': vale la pena di fare queste ottimizzazioni? La risposta naturalmente dipende da quello che devi sviluppare e dalla capacita' di calcolo che hai a disposizione. Per questo motivo questa ottimizzazione viene ampiamente utilizzata e, insieme ad altri accorgimenti simili, riescono ad ottimizzare le prestazioni fino al 30%. Il 30% di ottimizzazione sul software permette di acquistare processori meno performanti ma piu' economici, ed e' un vantaggio se hai intenzione di vendere milioni di esemplari,no?
Ripeto:
Se il valore di post-incremento non viene utilizzato pre e post-incremento sono la stessa cosa.
Non posso giurare che in una condizione complessa come l'incremento di un iteratore (dove viene chiamato l'operator++() dell'iteratore) questa ottimizzazione abbia effetto, perché dipende dal fatto che la funzione "operator++()" venga o meno inline-ata (scusate il neologismo) nella funzione chiamante o che il front-end rilevi in anticipo l'ottimizzazione e chiami la funzione operatore di pre-incremento anziché di post-incremento direttamente, ma nella situazione classica di valori numerici di base (float, int, char ... etc) qualsiasi compilatore ottimizzante che io conosca la applica e se non lo fa e' buggato :)
sottovento
10-09-2014, 19:48
Ripeto:
Se il valore di post-incremento non viene utilizzato pre e post-incremento sono la stessa cosa.
Non posso giurare che in una condizione complessa come l'incremento di un iteratore (dove viene chiamato l'operator++() dell'iteratore) questa ottimizzazione abbia effetto, perché dipende dal fatto che la funzione "operator++()" venga o meno inline-ata (scusate il neologismo) nella funzione chiamante o che il front-end rilevi in anticipo l'ottimizzazione e chiami la funzione operatore di pre-incremento anziché di post-incremento direttamente, ma nella situazione classica di valori numerici di base (float, int, char ... etc) qualsiasi compilatore ottimizzante che io conosca la applica e se non lo fa e' buggato :)
Perdonami, non ho sottomano in questo momento nessun processore di tipo militare, come avevo fatto notare. Pero' quando l'avevo, ho controllato e verificato al 100% che pre e post GENERANO CODICE COMPLETAMENTE DIVERSO.
Stai affermando che sul tuo computer con il tuo compilatore non c'e' differenza. Prendo atto. Hai poi esteso il ragionamento dicendo che non ci sono processori/compilatori che genereranno mai codice diverso per i due tipi di istruzione poiche' questa fase e' antecedente alla fase di generazione del codice per lo specifico processore. E' corretto?
Purtroppo non ho altro da aggiungere....
Semplicemente mi è stato insegnato ad utilizzare la testa e andare per logica.
In un esempio come quello proposto le performance del pre-incremento Vs. post-incremento possono essere solo migliori o uguali e allora:
- Se non mi serve il valore prima dell'incremento allora utilizzo il pre-incremento (a prescindere da possibili/eventuali ottimizzazioni che non governo esplicitamente)
- Se mi serve il valore prima dell'incremento allora utilizzo il post-incremento
Se mi viene insegnata una cosa che ritengo logica (eseguire tendenzialmente il minor numero di operazioni possibili) non vedo motivi per non applicarla.
Poi ognuno creda e/o faccia ciò che vuole.
funzionava così con i compilatori anni 70 e anni 80 che erano meno "intelligenti" rispetto ai compilatori moderni.
per i compilatori moderni non c'è la minima differenza prestazionale tra usare un post ed un preincremento semplicemente perchè, dove viene ritenuto possibile il compilatore taglia le istruzioni inutili.
nei programmi che sviluppo abitualmente solamente cambiando compilatore dal gcc ad uno commerciale (IAR) riesco ad ottenere mediamente un 20% di incremento delle prestazioni del sw che, visto che lavoro con quantità limitate di memoria e potenza di calcolo è un bel vantaggio :D
Silvia2003
11-09-2014, 16:05
Una curiosità: ultimamente vedo spesso l'incremento di i nei cicli utilizzando un preincremento invece di un postincremento... c'è un motivo o è solo cambiata la moda? Immagino la seconda ma sono curioso anche dell'origine di questa moda.
x !fazz
Quindi alla domanda come risponderesti? Qual'è l'indicazione utile?
sottovento
11-09-2014, 16:10
sottovento, non capisco a cosa ti riferisci per processori MIL-, io nel settore militare per un paio di anni ci ho lavorato ed i processori usati in radio tattiche o sistemi di bordo erano normalissimi ARM o DSP Texas, quando non si ricorreva direttamente ad Intel se le installazioni lo consentivano.
Capisco. Niente che andasse fuori dall'atmosfera, giusto?
Comunque se volete conferma sull'esistenza o meno di codice specifico per pre o post incrememento, nulla di meglio che chiedere a cdimauro che è una bibbia vivente per le varie architetture... :)
Piccolo esempio, senza scomodare architetture strane: http://en.wikipedia.org/wiki/Motorola_68000
Instruction set details
The standard addressing modes are:
Register direct
data register, e.g. "D0"
address register, e.g. "A6"
Register indirect
Simple address, e.g. (A0)
Address with post-increment, e.g. (A0)+
Address with pre-decrement, e.g. -(A0)
Address with a 16-bit signed offset, e.g. 16(A0)
Register indirect with index register & 8-bit signed offset e.g. 8(A0, D0) or 8(A0, A1)
Note that with (A0)+ and -(A0), the actual increment or decrement value is dependent on the operand size: a byte access increments the address register by 1, a word by 2, and a long by 4.
Ovviamente ci sono compilatori (il processore e' un po' vecchiotto ma ancora in uso) che ottimizzano il codice generato ed utilizzano le istruzioni di cui sopra.
Ricapitolando: esistono processori che hanno istruzioni di pre-decremento, post-incremento, ecc. Questo e' un fatto incontrovertibile.
I compilatori fatti per ottimizzare il codice per quei processori utilizzano queste specifiche istruzioni per avere prestazioni migliori.
AnonimoVeneziano
11-09-2014, 16:53
Capisco. Niente che andasse fuori dall'atmosfera, giusto?
Piccolo esempio, senza scomodare architetture strane: http://en.wikipedia.org/wiki/Motorola_68000
Instruction set details
The standard addressing modes are:
Register direct
data register, e.g. "D0"
address register, e.g. "A6"
Register indirect
Simple address, e.g. (A0)
Address with post-increment, e.g. (A0)+
Address with pre-decrement, e.g. -(A0)
Address with a 16-bit signed offset, e.g. 16(A0)
Register indirect with index register & 8-bit signed offset e.g. 8(A0, D0) or 8(A0, A1)
Note that with (A0)+ and -(A0), the actual increment or decrement value is dependent on the operand size: a byte access increments the address register by 1, a word by 2, and a long by 4.
Ovviamente ci sono compilatori (il processore e' un po' vecchiotto ma ancora in uso) che ottimizzano il codice generato ed utilizzano le istruzioni di cui sopra.
Ricapitolando: esistono processori che hanno istruzioni di pre-decremento, post-incremento, ecc. Questo e' un fatto incontrovertibile.
I compilatori fatti per ottimizzare il codice per quei processori utilizzano queste specifiche istruzioni per avere prestazioni migliori.
Il fatto che ci siano notazioni speciali per l'incremento o il decremento integrate negli operandi delle istruzioni non cambia assolutamente il discorso.
Faccio un esempio che si applica al ciclo for che stiamo considerando.
Immaginiamo una ipotetica architettura inventata che possiede, tra le varie, queste istruzioni:
mov rX, $Y -- Muove un valore in registro
cmp rX, rY -- Compara due valori e setta le flag
cmp rX, (rY)+ -- Compara due valori, setta le flags e incrementa il secondo dopo la comparazione
cmp rX, +(rY) -- Incrementa il secondo valore e poi compara e setta le flag
br.eq $label -- Salta alla label indicata se la comparazione ha trovato valori uguali
Quest'architettura ha istruzioni "speciali" per il pre e post incremento del tutto simili a quelle che intendi tu.
ora, il ciclo for con il pre-incremento in maniera NAIVE potrebbe tradursi in:
mov r1, $1000
mov r0, $0
LOOP:
-- Corpo del loop .... ---
cmp r1, +(r0)
br.eq $LOOP
quello con post-incremento
mov r1, $1000
mov r0, $0
LOOP:
-- Corpo del loop .... ---
mov r2, r0
cmp r1, +(r0)
br.eq $LOOP
Come vedi la traduzione di entrambi viene fatta con il PRE-incremento (non e' un errore), questo perché l'incremento nel caso del ciclo deve essere fatto SEMPRE PRIMA della comparazione. L'unica differenza tra i due e' quell'extra "mov r2, r0" che salva il valore di r0 prima che venga incrementato , così che istruzioni che usano il valore prima dell'incremento lo possano utilizzare.
Qualsiasi compilatore semi-buono potrebbe vedere con facilita' che quella copia di r0 e' una istruzione morta. Ergo, la DCE la eliminerebbe trasformando PER ENTRABI I CASI il loop in:
mov r1, $1000
mov r0, $0
LOOP:
-- Corpo del loop .... ---
cmp r1, +(r0)
br.eq $LOOP
Non c'e' traduzione piu' efficiente di quella che ho postato per il ciclo in questione su questa architettura :)
Spero che l'esempio abbia chiarificato la questione.
x !fazz
Quindi alla domanda come risponderesti? Qual'è l'indicazione utile?
Se vuoi il mio personale parere, usa quello che preferisci che con i compilatori attuali non c'è differenza se preferisci usare il pre incremento usalo tranquillamente
Capisco. Niente che andasse fuori dall'atmosfera, giusto?
Piccolo esempio, senza scomodare architetture strane: http://en.wikipedia.org/wiki/Motorola_68000
Instruction set details
The standard addressing modes are:
Register direct
data register, e.g. "D0"
address register, e.g. "A6"
Register indirect
Simple address, e.g. (A0)
Address with post-increment, e.g. (A0)+
Address with pre-decrement, e.g. -(A0)
Address with a 16-bit signed offset, e.g. 16(A0)
Register indirect with index register & 8-bit signed offset e.g. 8(A0, D0) or 8(A0, A1)
Note that with (A0)+ and -(A0), the actual increment or decrement value is dependent on the operand size: a byte access increments the address register by 1, a word by 2, and a long by 4.
Ovviamente ci sono compilatori (il processore e' un po' vecchiotto ma ancora in uso) che ottimizzano il codice generato ed utilizzano le istruzioni di cui sopra.
Ricapitolando: esistono processori che hanno istruzioni di pre-decremento, post-incremento, ecc. Questo e' un fatto incontrovertibile.
I compilatori fatti per ottimizzare il codice per quei processori utilizzano queste specifiche istruzioni per avere prestazioni migliori.
riguardo ai processori mil grade guarda che non hanno niente di più ne di meno rispetto ai processori standard a livello di istruzioni componenti ecc ecc sia per uso terreste che spaziale, l'unica cosa che cambia è la maggiore schermatura per le radiazioni ionizzanti
esempio i primi shuttle andavo in orbita con 3 zilog z80 (zx spectrum o gameboy tanto per capirci) gli ultimi mi pare con degli 80286 (ma dovrei verificare) ma l'instuction set è identico
altro esempio la marina militare degli stati uniti ha tutti i sistemi di bordo basati su Microsoft Windows NT su parecchie navi e li usano industrial pc con processori x86 tradizionali
riguardo all'eletrronica militare non di avionica (quindi senza i limiti stringenti di protezione em) ti assicuro che quella è fatta tranquillamente con componentistica cots di diversi produttori dai processori TI, STM alle fpga della Xilinx
per come la vedo io siete in torto entrambi, inoltre non state considerando cpu moderne, non state neanche considerando un Pentium 4, se andate indietro indietro fino al Motorola 68k non avrete mai il quadro completo .
www.youtube.com/watch?v=eh8WETRT7q4#t=11m08s
alla spiegazione data nel video si potrebbero aggiungere altre argomentazioni sul perché e come è meglio iterare o agire su un array o su un blocco di memoria contiguo, ma siamo già abbondantemente OT, vi dico solo che le moderne CPU funzionano in maniera molto differente da quello che vi state immaginando e a volte neanche uno sguardo al codice assembly vi detta un comportamento affidabile e deterministico.
Come sempre il sorgente che avete in mano quasi mai è il codice che alla fine viene eseguito, il potere delle CPU moderne è mantenere viva l'illusione del determinismo .
sottovento
11-09-2014, 17:27
riguardo ai processori mil grade guarda che non hanno niente di più ne di meno rispetto ai processori standard a livello di istruzioni componenti ecc ecc sia per uso terreste che spaziale, l'unica cosa che cambia è la maggiore schermatura per le radiazioni ionizzanti
Beh, a livello elettrico cambia ancora qualcosina. E poi non c'e' la ventola :D
esempio i primi shuttle andavo in orbita con 3 zilog z80 (zx spectrum o gameboy tanto per capirci) gli ultimi mi pare con degli 80286 (ma dovrei verificare) ma l'instuction set è identico
No dai, con PROCESSORI piu' o meno della stessa potenza, ma non quelli! Quelli non funzionerebbero. Parlo quanto meno per le missioni che ho fatto io, se qualcun altro ha fatto missioni spaziali con quei processori, si faccia avanti.
AnonimoVeneziano
11-09-2014, 17:49
no, sistemi di terra, non aerospaziali.
interessante, non lo sapevo. oltre ai motorola su cosa hai lavorato con istruzioni simili?
Anche ARM ha da una vita istruzioni con pre-post incremento per l'accesso alla memoria lineare, fin da versioni molto vecchie dell'instruction set:
http://www.cs.uregina.ca/Links/class-info/301/ARM-addressing/lecture.html
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.