#define e const byte e le loro reali differenze

nid69ita:
Diavolaccio !! :smiling_imp: :smiling_imp: :grin:

P.S. Che brutto però un cast in un define (non mi piace molto)
Ricordiamo poi quello che disse (giustamente) @astro, non è detto che tutti i compilatori, dato gli esempi fatti con la const, la trattino alla pari di un define; ovvero non è detto sia portabile.

Nemmeno a me piace troppo castare, men che meno in una macro
ma separiamo il luppolo dall'olio (o forse la citazione non è proprio così..........)
la #define è una direttiva di PRE-compilatore, il compilatore non c'entra nulla, si tratta di una pura sostituzione di testo
Semmai alcuni compilatori possono trattare trattare variabili definite "const", inizializzate a valore pre-conosciuto e non movimentate come se fossero scritte "hard coded", ma sarebbe una ottimizzazione dl compilatore e "deve" essere escludibile, come non lo so e non mi interessa, ma "deve" esserci, per poter usare "Extern" quelle variabili
ma questo non varrebbe ad esempio se io scrivessi una cosa del tipo

const int costante= analogRead(A0);

avrei una costante che non è definita a priori (a compile-time), in questo caso il compilatore ottimizzerà ben poco...

Standardoil:
Ritenta sarai più fortunato -> Nessun warning
comunque perchè undefined behavior? non c'è nessuna ambiguità in quello che ho scritto
modificare una variabile puntata da un puntatore valido NON è sbagliato.....

No, eh?

/home/sukko/Development/arduino/test/test.ino: In function 'void loop()':
/home/sukko/Development/arduino/test/test.ino:16:25: warning: invalid conversion from 'const int*' to 'int*' [-fpermissive]
     incrementa(&costante);
                         ^
/home/sukko/Development/arduino/test/test.ino:22:6: note:   initializing argument 1 of 'void incrementa(int*)'
 void incrementa(int * pointer)
      ^

Mica per niente:

Putting the address of a const type into a pointer to the unqualified type is [...] dangerous and consequently prohibited (although you can get around this by using a cast). Here is an example:

#include <stdio.h>

#include <stdlib.h>

main(){
int i;
const int ci = 123;

/* declare a pointer to a const.. */
const int *cpi;

/* ordinary pointer to a non-const */
int *ncpi;

cpi = &ci;
ncpi = &i;

/*

  • this is allowed
    */
    cpi = ncpi;

/*

  • this needs a cast
  • because it is usually a big mistake,
  • see what it permits below.
    */
    ncpi = (int *)cpi;

/*

  • now to get undefined behaviour...
  • modify a const through a pointer
    */
    *ncpi = 0;

exit(EXIT_SUCCESS);
}




As the example shows, it is possible to take the address of a constant object, generate a pointer to a non-constant, then use the new pointer. This is an **error** in your program and results in undefined behaviour.

(Da The C Book — Const and volatile)

E se vuoi anche il riferimento allo standard:

The C Standard, 6.7.3, paragraph 6 [ISO/IEC 9899:2011], states

If an attempt is made to modify an object defined with a const-qualified type through use of an lvalue with non-const-qualified type, the behavior is undefined.

(Da EXP40-C. Do not modify constant objects - SEI CERT C Coding Standard - Confluence)

Ritenta ascoltando chi ne sa più di te, sarai più fortunato. O per lo meno farai meno figuracce.

Standardoil:
la #define è una direttiva di PRE-compilatore, il compilatore non c'entra nulla

ovvio.
Per inciso io ho scritto solo di const la non sicurezza che ogni compilatore lo ottimizzi.

io non posto boiate
il codice che ho postato compila correttamente senza alcun warning e comportandosi correttamente
se vuoi ti metto l'output del compilatore non ho problemi
come linea di principio io parlo sempre a ragion veduta, vediamo di farcene una ragione, scusate il gioco di parole
il problema, anzi, il "tra virgolette problema" di quel codice è solo che la variabile

const int costante=100;

va in realtà dichiarata

volatile const int costante=100;

per avvisare il compilatore che NON deve fare ottimizzazioni su di essa
così facendo il programma si comporta correttamente
pronto a disposizione per postare programma e output di compilazione
come mai non vada a te, è un problema non mio..................

Preciso che il warning di cui sopra l'ho ottenuto prendendo il tuo codice e compilandolo con Arduino 1.8.6. Se a te il warning non esce evidentemente hai un problema nell'installazione o hai zittito l'output, perché DEVE uscire.

Un programma si comporta correttamente quando il compilatore fa sì che faccia quel che descrive il suo codice sorgente nel rispetto dello standard del linguaggio. Nel caso in esame lo standard NON definisce cosa il compilatore debba fare per cui qualunque comportamento è giusto, anche ucciderti il gatto. E il fatto che tu ottenga due comportamenti diversi a fronte di una lieve modifica è la prova di questo. Anche perché se una qualunque ottimizzazione del compilatore modificasse il comportamento del programma, sarebbe un bug del compilatore.

Poi beh, capisco che tu non voglia credere a me (anche se sono probabilmente più qualificato di te), e nemmeno al primo sito che capita, ma almeno di fronte allo standard C dovresti avere l'umiltà di arrenderti.

Boh, ognuno crederà a chi vuole. Però noto che questa cosa dell'undefined behavior proprio non entra in testa a molti. In qualche altro thread avevo già litigato con qualcuno che non voleva ammettere che i = i++; fosse UB neppure a fronte del warning "behaviour might be undefined" emesso da GCC :confused:.

Mah, guarda
io non discuto con te, so che sei più qualificato
io uso il pinguino, con IDE 1.8.5, versione stand-alone, tutti i flag di spunta inseriti
non sono programmator per lavoro, quindi non sto nemmeno a pasticciare con le varie opzioni, quello che c'è lascio
al massimo ho installato qualche libreria, che però qui non uso........
adesso per curiosità provo la 1.8.6 stand-alone, giusto per prova, visto che comunque squadra che vince non si cambia
la "storia" di passare un puntatore a una funzione per modificare una costante non me la sono inventata io, la ho copiata, anche se non ricordo da dove, lo sto cercando adesso........

MA VUOI CAPIRE CHE NON SI FA E BASTA???

Se hai la necessità di modificare qualcosa che hai dichiarato const hai sbagliato a dichiararlo tale, punto. Non ti pare??? C'è davvero da discutere?

Sì, concordo, ma tutto questo non ha nulla a che fare con l'argomento del topic
che è: "che differenze c'è tra const e #define"?
eccone una.....
che poi sia più o meno corretto fare quello che ho fatto, è evidente che una variabile col modificatore const alloca memoria, è indirizzabile e passabile per indirizzo, la #define non fa nulla di tutto questo
che poi sia sbagliato oppur no fare quello che ho fatto io NON è l'argomento del topic, grazie per l'interessamento.......... ma adesso chiudiamola!

Io sarei l'ultimo a dover parlare, ma...
permettere la modifica di qualcosa dichiarato const dopo la dichiarazione mi parrebbe un bel controsenso.
Però non mi è chiaro se usando i puntatori questo sia fisicamente possibile.
Il compilatore è sempre in grado di accorgersene? Lo impedisce fisicamente con un errore di compilazione o compilando ignorando l'operazione? oppure la permette?
Dal risultato in 16 pare abbia ignorato l'operazione.

Il compilatore se ne accorge quasi sempre (puoi far sì che non se ne accorga giocando coi puntatori, ma siamo sempre lì: PERCHÉ dovresti farlo???). In ogni caso, se fai una cosa del genere il compilatore non ha direttive precise da seguire (UB), per cui può succedere qualunque cosa, e questo è stato dimostrato da Standardoil che con una piccola modifica ha ottenuto due comportamenti diversi.

Forse ci siamo persi un po' di cose, ricapitoliamo:

  • #define fa una sostituzione testuale, per cui per forza di cose non viene allocata alcuna variabile ed è come scrivere il valore direttamente al posto del nome della costante.
  • const messo davanti ad una variabile dice al compilatore "questa variabile non dovrebbe mai cambiare valore, se lo cambio cazziami", e questo viene fatto tramite warning o errori di compilazione.
  • Qualunque compilatore un po' furbo non andrà a sprecare memoria per qualcosa che non cambia mai, e di fatto produrrà lo stesso codice che se si fosse usato #define.
  • Un compilatore non è tenuto ad essere furbo per rispettare lo standard C, è solo tenuto a rispettare i comportamenti che lo standard descrive, come lo fa è una scelta sua.
  • Di solito i compilatori prevedono diversi livelli di "furbizia" (= ottimizzazione), da "nessuna" (= spreca memoria) a "esagerata" (= impiega ore a compilare nel tentativo di migliorare le performance), passando per "pancia piatta" (= risparmia più memoria possibile o riduci la dimensione dell'eseguibile il più possibile).
  • Essendo gcc PARECCHIO furbo mi aspetto che (con le ottimizzazioni attive) anche senza const, se le circostanze lo permettono, non allochi memoria per una variabile ma la tratti al pari di una costante e quindi di una #define.
  • Andare a modificare una cosa dichiarata const, sebbene fattibile attraverso sotterfugi (che quasi sempre producono almeno un warning), risulta in UNDEFINED BEHAVIOUR, ovvero qualunque cosa il compilatore faccia, dal niente allo scatenare la 3a guerra mondiale, ricade perfettamente nello standard e non si può dire che sia sbagliato.
  • Ci sono altri casi di undefined (o unspecified) behaviour, è compito del programmatore tenersene alla larga.

Tutto chiaro?

no, al #16 non ha ignorato l'operazione, ma ha erroneamente ottimizzato per non ri-leggere la variabile alla stampa successiva
forzarla volatile obbliga il compilatore a ri-leggere sempre le variabili dalla memoria, e se si fa così, magia, il valore viene "correttamente" mostrato modificato
correttamente tra virgolette, dato che non è corretto farlo, su questo sono d'accordo con Sukko
ma lo fa, lo permette, generando (ma a me no, la 1.8.5 no, la 1.8.8 appena scaricata sì) un paio di warning, che a rigore NON interrompono la compilazione, segnalano solo al programmatore: "Ma sei sicuro sicuro di quello che stai facendo?, va' che io lo faccio, poi dopo sono cavoli tuoi........"
se il programmatore è "sicuro sicuro" fa un bel cast a int * e così convince il compilatore a non dare warning, ed ecco che la variabile const viene modificata senza warning
poi che sia corretto... non lo è!
però ci sono altri casi, meno estremi, nei quali una variabile puo' essere const da una parte e non cosnt da un'altra

// versione 2
int variabile = 100;
void setup(void)
{
    Serial.begin(9600);
}

void loop(void)
{
    Serial.print("La variabile vale: ");
    stampa(variabile);
    incrementa(&variabile);
    Serial.print("Adesso vale: ");
    stampa(variabile);
}


void stampa(int costante)
{
    Serial.print(costante);
    Serial.println(" ma da qui non la posso modificare");
}

dove evidentemente la stessa variabile è modificabile da una funzione, costante per un'altra
eggia' che una variabile sia modificabile da una funzione è un po' strano per il 'C'

C'è una cosa che continua a sfuggirti: ciò che è CORRETTO e ciò che non lo è non lo decidi tu, bensì lo standard C a cui il compilatore aderisce!

Ciò che è CORRETTO secondo lo standard non sempre corrisponde a ciò che TU TI ASPETTI sia corretto.

Nel caso specifico, lo standard NON SPECIFICA quale sia il comportamento corretto da tenere, per cui non esiste nessun comportamento corretto (oppure lo sono tutti, a seconda di come la vuoi vedere).

Cosa non è chiaro di tutto questo?

SukkoPera:
Il compilatore se ne accorge quasi sempre (puoi far sì che non se ne accorga giocando coi puntatori, ma siamo sempre lì: PERCHÉ dovresti farlo???). In ogni caso, se fai una cosa del genere il compilatore non ha direttive precise da seguire (UB), per cui può succedere qualunque cosa, e questo è stato dimostrato da Standardoil che con una piccola modifica ha ottenuto due comportamenti diversi.

  • Qualunque compilatore un po' furbo non andrà a sprecare memoria per qualcosa che non cambia mai, e di fatto produrrà lo stesso codice che se si fosse usato #define.

  • Un compilatore non è tenuto ad essere furbo per rispettare lo standard C, è solo tenuto a rispettare i comportamenti che lo standard descrive, come lo fa è una scelta sua.

  • Essendo gcc PARECCHIO furbo mi aspetto che anche senza const, se le circostanze lo permettono, non allochi memoria per una variabile ma la tratti al pari di una costante e quindi di una #define.

coe al solito dissento in qualcosa, non avermene a male
io non ho ottnuto due comportamenti differenti a causa dello UB, ma perchè il compilatore, NON sapendo che la variabile veniva modificata, ottimizzava,
la conferma si ottiene facile, basta far stampare la variabile (NON volatile) sia dalla loop che dalla incrementa

// di Nelson "StandardOil"
// Idea da sviluppare:
// Prova 'const' non costanti

void setup(void)
{
  Serial.begin(9600);
}

void loop(void)
{
  const int costante = 100;
  Serial.print("La costante vale: ");
  Serial.println(costante);
  incrementa(&costante);
  Serial.print("Però adesso vale: ");
  Serial.println(costante);
  delay(1000);
}

void incrementa(int * pointer)
{
  // incrementa una variabile
  (*pointer)++;
  Serial.print("e qui vale: ");
  Serial.println(*pointer);
}

è la stessa ragione per cui dobbiamo dichiarare volatile le variabili usate nelle ISR , anche ho dovuto dichiarare volatile la char array che conteneva il carico pagante, nel mio crivello universale di pochi giorni fa
come ho già detto tempo fa, un compilatore che ottimizzasse troppo e non allocasse variabili const sbaglirebbe, const si riferisce allo scope del file corrente, se un progetto avesse più di un file sorgente (che non è il caso di arduino, concordo) e avesse le stesse variabili Extern queste devono venire allocate

SukkoPera:
C'è una cosa che continua a sfuggirti: ciò che è CORRETTO e ciò che non lo è non lo decidi tu, bensì lo standard C a cui il compilatore aderisce!

Ciò che è CORRETTO secondo lo standard non sempre corrisponde a ciò che TU TI ASPETTI sia corretto.

Nel caso specifico, lo standard NON SPECIFICA quale sia il comportamento corretto da tenere, per cui non esiste nessun comportamento corretto (oppure lo sono tutti, a seconda di come la vuoi vedere).

Cosa non è chiaro di tutto questo?

Cosa NON E' CHIARO nel fatto che io sono d'accordo con te, ma NON C'ENTRA con l'argomento della discussione?

Vabbeh, io ci rinuncio, non c'è peggior sordo di chi non vuol sentire.

SukkoPera:
Il compilatore se ne accorge quasi sempre (puoi far sì che non se ne accorga giocando coi puntatori, ma siamo sempre lì: PERCHÉ dovresti farlo???). In ogni caso, se fai una cosa del genere il compilatore non ha direttive precise da seguire (UB), per cui può succedere qualunque cosa, e questo è stato dimostrato da Standardoil che con una piccola modifica ha ottenuto due comportamenti diversi.

Il mio interesse riguarda comprendere fino a che punto sono protetto da eventuali errori, quindi se il compilatore mi intercetta il 100% dei casi sono a posto altrimenti ho comunque un margine di incertezza perchè aver fatto degli errori e non accorgermene subito.

Ovvio che con le tecniche di hacking riesco a scrivere quello che voglio su qualunque area di memoria, ma a parte queste "forzature", nei casi "normali" dovrei essere quanto meno avvisato.
Parlo di GCC, non dei compilatori in generale, perchè nei miei casi mi sa che userei sempre e solo questo o suoi derivati.

E allora stai sereno, i casi normali sono coperti.

Credo che quello di cui stiamo parlando sia una direttiva del linguaggio, non una iniziativa di gcc. const int non è int, per cui avrai sempre almeno un warning se passi il primo ad una funzione che si aspetta il secondo, con qualunque compilatore.

Il C++ poi è ancora più pignolo, perché puoi avere metodi const che non puoi invocare se hai un puntatore ad un oggetto non const, ecc ecc.
EDIT: Ovviamente quanto sopra è permesso, quel che non lo è è chiamare un metodo non-const tramite un puntatore const.

Scusate, mi riprendo un attimo la scena... Intanto GRAZIE a tutti per le molteplici risposte, per le spiegazioni tecniche fornite e per il bel dibattito che se ne era formato... Adesso mi è molto più chiara la differenza, ma se posso (Standardoil non me ne dire) vado per le corte:
const int SOMMA;
Non mi vado a complicare la vita per una dichiarazione errata di una variabile che ho fatto io e non il compilatore... Che poi ci sia un modo, più o meno convenzionale/testato/non supportato, di "cambiare" il valore di una const, beh è un'altra storia... e comunque penso che sia sempre più facile pigiare 5 volte backspace con il "puntatore" alla fine di "const" :smiley:
Non è per ridicolizzare niente e nessuno... Anzi, devo studiare bene la parte dei puntatori, referenziare, dereferenziare, ecc..
Ultima domanda che mi è sorta a seguito delle affermazioni di SukkoPera... Per OTTIMIZZAZIONE intendi l'LTO?

Intendo le normali ottimizzazioni di spazio occupato/performance, che avvengano durante la compilazione o durante il linking non inficia il discorso.