Este código não irá simplesmente inverter os bits do inteiro de 16 bits como esperado:
#include <stdint.h>
typedef uint16_t U16;
U16 myNum = 0xffff;
~myNum // 0xffff0000, not 0x0000
Porque C promoverá o inteiro de 16 bits para um inteiro de 32 bits antes de fazer a inversão (veja 1 , 2 ).
Quais são minhas opções para inversão bitwise de 16 bits? Posso pensar em:
~myNum & 0xffff
// or
((U16)~myNum)
Mas eles parecem propensos a erros/mal-entendidos. Será que estou sem sorte se eu quiser uma inversão de 16 bits bacana?
História curta: basta retornar ao tipo pretendido,
(uint16_t)~myNum
Longa história: O problema é que, de fato, devido à promoção implícita, quase todos os operadores C que começaram em um tipo inteiro pequeno, como
char
/short
etc, acabam usandoint
type em vez disso. Isso é particularmente problemático no caso de você ter umuint16_t
em uma máquina de 32 bits e aplicar~
, porque o operando será promovido para o tipoint
, o resultado será do tipoint
e, portanto, um valor negativo neste caso.As melhores práticas gerais com
~
é sempre converter o resultado de volta para o tipo pretendido, neste caso(uint16_t)~myNum
. A conversão para unsigned é bem definida conforme a tomada do valor matemático módulo 2^n, onde n é o número de bits no tipo unsigned. Ou se preferir, simplesmente descarte os bytes superiores doint
, deixando-nos com 0x0000.Além disso, mesmo que haja promoções implícitas presentes, o compilador é inteligente o suficiente para dizer que, no caso de
(uint16_t)~myNum
não haver efeitos colaterais não intencionais, como mudança de sinal. Então, ele poderia otimizar essa expressão para ser executada em tipos de 16 bits no código da máquina, se isso levar a uma execução mais rápida (certamente o caso em CPUs de 8/16 bits). Enquanto algo comoif(~myNum > 0)
não pode ser executado em 16 bits, porque a conversão implícita paraint
causou uma mudança de sinal e deu um valor negativo.A melhor prática é nunca escrever código contendo ou contando com promoção implícita, ou fingir que ela não está lá. Por exemplo,
myNum = ~myNum;
funciona também, mas esse código pode significar duas coisas: ou o programador pensou em promoção implícita, mas percebeu que não importa nesse caso específico. Ou o programador não pensou em promoção implícita e apenas acertou o código por sorte. Enquanto a melhor práticamyNum = (uint16_t)~myNum;
significa que o programador definitivamente considerou isso e, além disso, isso silenciará avisos de conversão implícita assinados para não assinados do compilador/analisador estático.