ProgrammazioneProgrammatore di sistema in C

Spiega le caratteristiche della conversione dei tipi tra numeri con segno diverso (signed/unsigned) in C, quali insidie possono sorgere, come evitare conseguenze impreviste durante l'aritmetica e il confronto dei tipi signed/unsigned e come questo influisce sulla portabilità dei programmi?

Supera i colloqui con l'assistente IA Hintsage

Risposta

In C, la conversione automatica dei tipi avviene secondo il principio delle "usual arithmetic conversions". Quando sono coinvolti in un'espressione numeri dichiarati con segno diverso (signed/unsigned), si applicano le seguenti regole:

  • Se uno degli operandi è unsigned e l'altro è signed, il signed viene automaticamente convertito in unsigned.
  • Questo può portare a sovraccarichi inaspettati, specialmente durante confronti o operazioni aritmetiche.
  • Anche la dimensione dei tipi influisce: se l'unsigned ha una maggiore lunghezza, il signed viene convertito in unsigned.

Esempio di aritmetica pericolosa:

int a = -1; // signed unsigned int b = 1; printf("%d\n", a < b); // sempre false, poiché a viene convertito in un unsigned molto grande

Il risultato: -1, convertito in unsigned, diventa un numero positivo molto grande.

Cosa ricordare:

  • Conversione esplicita dei tipi se c'è possibilità di confusione con il segno.
  • Prestare attenzione alle dimensioni dei tipi (int, long, uint32_t, ecc.), in modo che le conversioni siano prevedibili.
  • Separare la logica di lavoro con variabili signed e unsigned, specialmente nei controlli di confine e nell'aritmetica.

Domanda trabocchetto

Domanda: Che risultato restituirà l'espressione (int)(unsigned)-1?

Risposta errata attesa: "-1, perché -1 e stiamo convertendo in int."

Risposta corretta: Nell'espressione (unsigned)-1 avviene prima la conversione di -1 in unsigned (su una piattaforma a 32 bit questo è 0xFFFFFFFF), poi di nuovo in signed int, che dipende anche dall'implementazione, ma spesso si ottiene di nuovo -1 (se si usa il two's complement). Tuttavia, è più corretto dire: Il risultato dipende dagli standard di rappresentazione dei numeri signed, ma nella maggior parte delle implementazioni risulterà -1.

Esempio:

int x = (int)(unsigned)-1; // x == -1 su la maggior parte delle piattaforme

Esempi di errori reali dovuti alla mancanza di conoscenza delle sottigliezze dell'argomento


Storia

Nell'handler delle stringhe è stata utilizzata una funzione di confronto delle lunghezze: se la lunghezza della stringa poteva essere negativa, il programma segnalava un errore. Tuttavia, la lunghezza era di tipo size_t (unsigned) e il confronto con if(length < 0) restituiva sempre false, causando un ciclo infinito e overflow della memoria.


Storia

Durante il parsing del protocollo, i pacchetti di rete contenevano campi come unsigned, mentre le variabili locali erano signed. A causa del sovraccarico dell'unsigned durante l'elaborazione di alcuni valori, si sono verificati errori nei calcoli della lunghezza del pacchetto, portando a vulnerabilità di overflow del buffer.


Storia

Il modulo di confronto delle date nei log memorizzava la data come unsigned int, mentre cercava un intervallo di date in int. Alcuni valori limite, invece della risposta attesa di eccezione, portavano a una filtrazione errata delle registrazioni e alla perdita di log importanti.