Em esta resposta , Zwol fez esta afirmação:
A maneira correta de converter dois bytes de dados de uma fonte externa em um número inteiro assinado de 16 bits é com funções auxiliares como esta:
#include <stdint.h>
int16_t be16_to_cpu_signed(const uint8_t data[static 2]) {
uint32_t val = (((uint32_t)data[0]) << 8) |
(((uint32_t)data[1]) << 0);
return ((int32_t) val) - 0x10000u;
}
int16_t le16_to_cpu_signed(const uint8_t data[static 2]) {
uint32_t val = (((uint32_t)data[0]) << 0) |
(((uint32_t)data[1]) << 8);
return ((int32_t) val) - 0x10000u;
}
Qual das funções acima é apropriada depende se a matriz contém uma pequena representação endian ou big endian. Endianness não é o problema em questão aqui, estou me perguntando por que o zwol subtrai 0x10000u
do uint32_t
valor convertido para int32_t
.
Por que essa é a maneira correta ?
Como evita o comportamento definido pela implementação ao converter para o tipo de retorno?
Como você pode assumir a representação do complemento de 2, como essa conversão mais simples falharia: return (uint16_t)val;
O que há de errado com esta solução ingênua:
int16_t le16_to_cpu_signed(const uint8_t data[static 2]) {
return (uint16_t)data[0] | ((uint16_t)data[1] << 8);
}
int16_t
0xFFFF0001u
não pode ser representado como int16_t
e na segunda abordagem 0xFFFFu
não pode ser representado como int16_t
.
int16_t
é definido pela implementação; portanto, a abordagem ingênua não é portátil.