Probabile BUG del compilatore ...

Oggi, mentre stavo giocherellando con l'IDE di Arduino, mi sono imbattuto in uno strano errore, fortunatamente, facilmente riproducibile ... :wink:

Notare che il BUG è presente sia nella 1.0.5 che nella 1.5.6-r2.

Aprite l'IDE e caricate l'esempio della libreria Wire -> master_reader ...

Se lo compilate così come è vedrete che non c'è alcun errore e la compilazione riesce. Provate ora a renderlo più generico sostituendo l'indirizzo fisso del device I2C, nella Wire.requestFrom() con una valore costante.

Dichiarate, ad inizio programma, una costante di tipo uint8_t :

const uint8_t myAddr = 0x02;

e correggete la riga nel loop() per utilizzare tale valore costante :

Wire.requestFrom(myAddr, 6);    // request 6 bytes from slave device #2

ricompilate e ... sorpresa :

master_reader.ino: In function 'void loop()':
master_reader.ino:25: error: ISO C++ says that these are ambiguous, even though the worst conversion for the first is better than the worst conversion for the second:
/Applications/Arduino 1.5.6-r2.app/Contents/Resources/Java/hardware/arduino/avr/libraries/Wire/Wire.h:58: note: candidate 1: uint8_t TwoWire::requestFrom(int, int)
/Applications/Arduino 1.5.6-r2.app/Contents/Resources/Java/hardware/arduino/avr/libraries/Wire/Wire.h:56: note: candidate 2: uint8_t TwoWire::requestFrom(uint8_t, uint8_t)

... un errore a cui non trovo alcuna giustificazione, anche perché, se andate a vedere dentro la libreria Wire, scoprite che il metodo requestFrom() è così definito :

uint8_t TwoWire::requestFrom(uint8_t address, uint8_t quantity, uint8_t sendStop)

La cosa più assurda è che vedrete scomparire l'errore (... ma non so se poi il tutto funzioni correttamente) facendo un casting sbagliato ! Difatti, se mettete :

Wire.requestFrom((uint16_t)myAddr, 6);    // request 6 bytes from slave device #2

la compilazione va a buon fine ... :astonished: :astonished: :astonished:

L'unico modo, ovviamente, per parametrizzare la cosa è NON usare una costante (... metodo che invece è sempre consigliabile), ma una #define. Difatti, se invece del const, mettete :

#define myAddr 0x02

... il valore viene semplicemente sostituito e tutto viene compilato correttamente ... :grin: :grin: :grin:

A rafforzare l'dea di un BUG nel compilatore presente nell'IDE di Arduino e il fatto che ... se si sostituise la Toolchain con la nuova (Toolchain 3.4.3, avr gcc 4.8.1) ... l'errore scompare ... ]:smiley: ]:smiley: ]:smiley:

Guglielmo

Allora la soluzione sarà presto disponibile con la 1.5.7. Per tua gioia. :grin: :grin:

Comunque a me il warning del casting lo da ugualmente anche con la TC 3.4.3.

Impostando

byte address  = 2;

ottengo

In file included from master_reader.ino:15:0:
D:\arduino-1.5.5-TC343\hardware\arduino\avr\libraries\Wire/Wire.h: In function 'void loop()':
D:\arduino-1.5.5-TC343\hardware\arduino\avr\libraries\Wire/Wire.h:58:13: note: candidate 1: uint8_t TwoWire::requestFrom(int, int)
     uint8_t requestFrom(int, int);
             ^
D:\arduino-1.5.5-TC343\hardware\arduino\avr\libraries\Wire/Wire.h:56:13: note: candidate 2: uint8_t TwoWire::requestFrom(uint8_t, uint8_t)
     uint8_t requestFrom(uint8_t, uint8_t);
             ^

Lo sketch usa 6.030 byte (21%) dello spazio disponibile per i programmi. Il massimo è 28.672 byte.
Le variabili globali usano 360 byte (14%) di memoria dinamica, lasciando 2.200 byte liberi per le variabili locali. Il massimo è 2.560 byte.

Anche con const byte naturalmente.
Mentre char e const char non ottengo nessun avviso.
Questo perché nella libreria è definito in ingresso uint8_t e non int8_t che corrisponde a char e non a byte (che è più usato).
Il compilatore non fa casting automatico e non trova corrispondenza con la libreria.
Non so se è voluto o è un errore; io propendo per il fatto che sia un comportamento voluto.
Col casting automatico se sbagli a passare un valore ad una funzione con overload potresti involontariamente eseguire una funzione diversa da quella voluta. Bene fa il compilatore a segnalarti l'errore.
E' il programmatore che deve conoscere il formato richiesto in ingresso dalle librerie. :grin:

Cosa ne pensi?

... ne penso ... che non ho capito il tuo discorso ... :grin: :grin: :grin:

La funzione è definita come :

uint8_t TwoWire::requestFrom(uint8_t address, uint8_t quantity, uint8_t sendStop)

quindi si aspetta degli uint8_t !!!

Se tu definisci una costante di tipo uint8_t e compili con l'IDE standard ... il compilatore segnala ERRORE e non va avanti, se compili con la nuova toolchain (gcc 4.8.1), il compilatore segnala WARNING e va avanti compilando il codice.

Tolte quindi tutte le altre "chiacchiere" ... c'è evidentemente un BUG nel vecchio compilatore ... ed ho anche capito da cosa è dovuto ... XD

Basta difatti aggiungere un cast (uint8_t) anche al secondo parametro e tutto va a posto ... indovina un po' perché ? :grin: :grin: :grin:

Guglielmo

X inscrizione

gpb01:
Tolte quindi tutte le altre "chiacchiere" ... c'è evidentemente un BUG nel vecchio compilatore ... ed ho anche capito da cosa è dovuto ... XD
Basta difatti aggiungere un cast (uint8_t) anche al secondo parametro e tutto va a posto ... indovina un po' perché ? :grin: :grin: :grin:
Guglielmo

E no, il quiz della domenica. Quanto tempo abbiamo per trovare la risposta? :grin:
(Dai, su, diccelo, son curioso come un gatto)

nid69ita:
E no, il quiz della domenica. Quanto tempo abbiamo per trovare la risposta? :grin:
(Dai, su, diccelo, son curioso come un gatto)

Dai nid che è facile ... guarda i messaggi di errore/warning e t'accorgi che ... è un problema di "overloding" ... non previsto :

candidate 1: uint8_t TwoWire::requestFrom(int, int)
candidate 2: uint8_t TwoWire::requestFrom(uint8_t, uint8_t)

... ma non considera un "cadidate 3" e "4" con le altre due possibili combinazioni :grin: :grin: :grin:

Guglielmo

Ma perchè con il primo parametro a cui passi un const uint_8 cercherebbe:

uint8_t TwoWire::requestFrom(const uint8_t, uint8_t)

e siccome non lo trova ha in alternativa gli altri due e nel dubbio si pianta ?
Nella nuova toolchain han cambiato qualcosa nel compilatore per questi casi allora.

Oppure perchè il primo (tralasciando const) è uint8_t e il secondo è 6 e quindi viene intepretato come int, perciò

uint8_t TwoWire::requestFrom(uint8_t, int)

non è definito ?

Per assurdo se è nel secondo caso allora con const e cast sul secondo dovrebbe compilare senza warning ?!?

Wire.requestFrom(myAddr, (uint8_t)6);

Sicuramente SI ...

... il vecchio avr gcc si pianta con un bel ERRORE, il nuovo ha almeno il buon gusto di dare un WARNING (... che comunque è in buona compagnia con tutti gli altri che genera il core di Arduino) e di andare avanti regolarmente con la compilazione :wink:

Guglielmo

Confermo che così compila anche con IDE 1.0.5

const uint8_t MyAddr=22;
void loop()
{ Wire.requestFrom(MyAddr,(uint8_t)6);

Quindi non è il const di MyAddr a dargli fastidio, ma considera il secondo parametro come int.

Però forse non è meglio l'errore della versione vecchia? Con la nuova compila, ma quale delle due sceglierà?
la int,int o la uint8_t,uint8_t ???

... NO, è meglio che vada avanti, visto che comunque poi all'interno la funzione tratta quel valore come un uint8_t e trasmette solo 7 bit di address (più uno di R/W) ... prenderà il byte meno significativo dell'int e lo userà :slight_smile:

La cosa giusta, in mancanza del corretto overload, è il warning che comunque ti avvisa :wink:

Guglielmo

nid69ita:
Con la nuova compila, ma quale delle due sceglierà?

gpb01:
... NO, è meglio che vada avanti, visto che comunque poi all'interno la funzione tratta quel valore come un uint8_t e trasmette solo 7 bit di address (più uno di R/W) ... prenderà il byte meno significativo dell'int e lo userà :slight_smile:

La cosa giusta, in mancanza del corretto overload, è il warning che comunque ti avvisa :wink:

Non sono d'accordo perché non sai quale funzione userà. E se in base al parametro decido di fare cose diverse?
A quel punto non so come si comporterebbe il mio programma.

PaoloP:
Non sono d'accordo perché non sai quale funzione userà. E se in base al parametro decido di fare cose diverse?
A quel punto non so come si comporterebbe il mio programma.

Paolo di cosa parli ??? :astonished:

Chi non sai che funzione userà ?

Stiamo parlando di un metodo, il metodo requestFrom(), della classe Wire e si sa esattamente cosa farà (... deve richiede N bytes al device indirizzato dai 7 bit di address) e come si comporterà il tuo programma ...

Guglielmo

i vari candidati, candidate 1, e 2 fanno parte del codice della libreria o del compilatore ?
se sono una cosa scritta in libreria basta aggiornare la libreria alle 4 possibili varianti

Nel .cpp della libreria troviamo :

uint8_t TwoWire::requestFrom(uint8_t address, uint8_t quantity, uint8_t sendStop)
{
  // clamp to buffer length
  if(quantity > BUFFER_LENGTH){
    quantity = BUFFER_LENGTH;
  }
  // perform blocking read into buffer
  uint8_t read = twi_readFrom(address, rxBuffer, quantity, sendStop);
  // set rx buffer iterator vars
  rxBufferIndex = 0;
  rxBufferLength = read;

  return read;
}

uint8_t TwoWire::requestFrom(uint8_t address, uint8_t quantity)
{
  return requestFrom((uint8_t)address, (uint8_t)quantity, (uint8_t)true);
}

uint8_t TwoWire::requestFrom(int address, int quantity)
{
  return requestFrom((uint8_t)address, (uint8_t)quantity, (uint8_t)true);
}

uint8_t TwoWire::requestFrom(int address, int quantity, int sendStop)
{
  return requestFrom((uint8_t)address, (uint8_t)quantity, (uint8_t)sendStop);
}

... ovvero due casi per due parametri e due casi per tre parametri, mancano i casi incrociati (unit8_t, int) e (int, uint8_t), quindi, volendo è facile aggiungerli.

E' anche chiaramente visibile che, comunque vengano scelti, alla fine sempre a uint8_t vengono portati, qualunque caso si sceglie (... e ci mancherebbe altro). :slight_smile:

Comunque, lo scopo del thread era un altro ... mostrare il comportamento di una versione di gcc rispetto alla versione successiva e mettere quindi in guardia sul tipo di problematica. :roll_eyes:

Nel caso della libreria specifica ... senza impazzire con tante modifiche ... basta un banale cast :grin:

Guglielmo

gpb01:
Comunque, lo scopo del thread era un altro ... mostrare il comportamento di una versione di gcc rispetto alla versione successiva e mettere quindi in guardia sul tipo di problematica. :roll_eyes:

Chiaro. Mi domando cosa e/o come hanno modificato il comportamento del compilatore in questi casi che una certa ambiguità la creano.

nid69ita:
Chiaro. Mi domando cosa e/o come hanno modificato il comportamento del compilatore in questi casi che una certa ambiguità la creano.

Bé ... i sorgenti di avr gcc dovrebbero essere disponibili ... buona lettura ...

Guglielmo

C++ è ISO standard ed è normale aspettarsi una evoluzione del C++.
https://isocpp.org/std/status

GCC ISO compliant, per cui c'è da vedere come e quando sarà fedele allo standard C++11, C++14 ecc, non è per niente un gioco da ragazzi.
Il comportamento evidenziato da gpb01 è tipico di C++98, c'è da vedere quale incarnazione di GCC usa Arduino, poi
non dimentichiamo che AVR-GCC segue un'altro standard detto Embedded C (almeno ci prova e quanto aderente sia non l'ho mai sondato) il quale non mi pare faccia riferimento a C++ quindi AVR-GCC si trova su due fronti diversi a seguire cosa? :wink:

Comunque mi sembra un problema risolvibile e la proposta di inserire un'altra funzione... io direi che due sono già superflue in ambito embedded. :stuck_out_tongue:

Ciao.

nid69ita:
il secondo è 6 e quindi viene intepretato come int

E' quanto ho pensato anch'io ieri, facendo i test quando Guglielmo mi ha informato della cosa. Però suona strano perché un numero è una costante e come tale con che criterio viene scelto il tipo (u)int16 piuttosto che il tipo (u)int8?

leo72:

nid69ita:
il secondo è 6 e quindi viene intepretato come int

E' quanto ho pensato anch'io ieri, facendo i test quando Guglielmo mi ha informato della cosa. Però suona strano perché un numero è una costante e come tale con che criterio viene scelto il tipo (u)int16 piuttosto che il tipo (u)int8?

Premetto che non sò quali algoritmi un compilatore segua per decidere quale funzione overloaded scelga.
Credo che il problema principale non sia tanto sul 8 o 16. La cifra 6 è ambigua anche in questo caso perchè potrebbe essere considerata 16 o 8 e non ci sarebbero problemi. Secondo me il vero dilemma è se considerarla signed o unsigned, nella lista di funzioni candidate è quella la vera discriminante per fare una scelta.
Per questo mi dicevo curioso di sapere chissà che algoritmi hanno implementato per queste scelte "quasi" intelligenti. Logico che non ho voglia di leggermi i sorgenti del gcc ne credo qualcuno abbia voglia di farlo. :smiley:

Il problema non è se signed o unsigned. Perché sai fai il cast esplicito ad entrambi i tipi ad 8 oppure a 16 bit, il compilatore non dà errore perché giustamente ha già i 2 casi overloadati. Ma nel caso di un int8 e di un numero, con che criterio stabilisce che una COSTANTE NUMERICA è int16?