Contesto
Stiamo eseguendo il porting del codice C che è stato originariamente compilato utilizzando un compilatore C a 8 bit per il microcontrollore PIC. Un linguaggio comune che è stato utilizzato per impedire che le variabili globali senza segno (ad esempio i contatori di errori) si spostassero indietro a zero è il seguente:
if(~counter) counter++;
L'operatore bit a bit qui inverte tutti i bit e l'istruzione è vera solo se counter
è inferiore al valore massimo. È importante sottolineare che funziona indipendentemente dalla dimensione della variabile.
Problema
Ora stiamo prendendo di mira un processore ARM a 32 bit utilizzando GCC. Abbiamo notato che lo stesso codice produce risultati diversi. Per quanto ne sappiamo, sembra che l'operazione di complemento bit a bit restituisca un valore di dimensioni diverse da quanto ci aspetteremmo. Per riprodurlo, compiliamo, in GCC:
uint8_t i = 0;
int sz;
sz = sizeof(i);
printf("Size of variable: %d\n", sz); // Size of variable: 1
sz = sizeof(~i);
printf("Size of result: %d\n", sz); // Size of result: 4
Nella prima riga di output, otteniamo ciò che ci aspetteremmo: i
è 1 byte. Tuttavia, il complemento bit per bit di in i
realtà è di quattro byte che causa un problema perché i confronti con questo ora non daranno i risultati previsti. Ad esempio, se lo fai (dove i
è correttamente inizializzato uint8_t
):
if(~i) i++;
Vedremo i
"avvolgere" da 0xFF a 0x00. Questo comportamento è diverso in GCC rispetto a quando funzionava come previsto nel precedente compilatore e microcontrollore PIC a 8 bit.
Siamo consapevoli di poterlo risolvere lanciando in questo modo:
if((uint8_t)~i) i++;
Oppure, di
if(i < 0xFF) i++;
Tuttavia, in entrambe queste soluzioni alternative, la dimensione della variabile deve essere nota ed è soggetta a errori per lo sviluppatore del software. Questi tipi di controlli sui limiti superiori si verificano in tutta la base di codice. Esistono più dimensioni di variabili (ad es.,uint16_t
E unsigned char
così via) e la modifica di questi in una base di codice altrimenti di lavoro non è qualcosa che stiamo guardando al futuro.
Domanda
La nostra comprensione del problema è corretta e ci sono opzioni disponibili per risolvere questo problema che non richiedono di visitare nuovamente ogni caso in cui abbiamo usato questo linguaggio? La nostra ipotesi è corretta, secondo cui un'operazione come il complemento bit per bit dovrebbe restituire un risultato delle stesse dimensioni dell'operando? Sembra che questo si rompa, a seconda delle architetture del processore. Mi sento come se stessi prendendo pillole pazze e che C dovrebbe essere un po 'più portatile di così. Ancora una volta, la nostra comprensione di questo potrebbe essere sbagliata.
In apparenza questo potrebbe non sembrare un grosso problema, ma questo idioma precedentemente funzionante viene utilizzato in centinaia di località e non vediamo l'ora di capirlo prima di procedere con costose modifiche.
Nota: qui c'è una domanda duplicata apparentemente simile ma non esatta: L' operazione bit a bit su char dà un risultato a 32 bit
Non ho visto il vero punto cruciale del problema discusso qui, vale a dire, la dimensione del risultato di un complemento bit per bit è diversa da quella passata nell'operatore.