linguagem C: messes valor #defined até 8-bit multiplicação. Por quê?
-
16-09-2019 - |
Pergunta
Eu tenho o seguinte código C:
#define PRR_SCALE 255
...
uint8_t a = 3;
uint8_t b = 4;
uint8_t prr;
prr = (PRR_SCALE * a) / b;
printf("prr: %u\n", prr);
Se eu compilar este (usando um compilador msp430 plataforma, para um pequeno OS incorporado chamado contiki ) o resultado é 0, enquanto eu esperava 191. (Uint8_t é typedef'ed como um unsigned char)
Se eu mudá-lo para:
uint8_t a = 3;
uint8_t b = 4;
uint8_t c = 255;
uint8_t prr;
prr = (c * a) / b;
printf("prr: %u\n", prr);
ele funciona corretamente e impressões 191.
Compilar uma versão simples deste 'normalmente' usando gcc em uma caixa de Ubuntu imprime o valor correto em ambos os casos.
Eu não sou exatamente certo por que isso é. Eu poderia contorná-la, atribuindo o valor definido para uma variável de antemão, mas eu prefiro não fazer isso.
Alguém sabe por que isso é? Talvez com um link para mais algumas informações sobre isso?
Solução
A resposta curta: você compilador é buggy. (Não há nenhum problema com o excesso, como outros sugeriram).
Em ambos os casos, a aritmética é feito em int
, que é garantida para ser pelo menos 16 bits de comprimento. No primeiro trecho é porque 255
é um int
, neste último é por causa de promoção integral .
Como você observou, alças gcc isso corretamente.
Outras dicas
255 está a ser processado como um número inteiro literal e faz com que toda a expressão a ser int baseado em vez de unsigned char base. A segunda forças de caso do tipo a ser corretas. Tente alterar o # define da seguinte forma:
#define PRR_SCALE ((uint8_t) 255)
Se o compilador em questão é o mspgcc, ele deve colocar para fora um montador listagem do programa compilado em conjunto com o arquivo binário / hexadecimal. Outros compiladores podem exigir bandeiras do compilador adicionais para fazê-lo. Ou talvez até mesmo uma corrida desmontador separado no binário.
Este é o lugar onde procurar uma explicação. Devido a otimizações do compilador, o código real apresentado ao processador pode não têm muita semelhança com o código original C (mas normalmente faz o mesmo trabalho).
Percorrendo as poucas instruções assembler representando o código defeituoso deve revelar a causa do problema.
Meu palpite é que o compilador de alguma forma otimiza todo o cálculo sice a constante definida é uma parte conhecida em tempo de compilação. 255 * x poderia ser optimizado para x << 8-x (o qual é mais rápido e menor) Talvez algo está errado com o código assembler otimizado.
eu levei um tempo para compilar ambas as versões no meu sistema. Com a otimização de ativos, o mspgcc produz o seguinte código:
#define PRR_SCALE 255
uint8_t a = 3;
uint8_t b = 4;
uint8_t prr;
prr = (PRR_SCALE * a) / b;
40ce: 3c 40 fd ff mov #-3, r12 ;#0xfffd
40d2: 2a 42 mov #4, r10 ;r2 As==10
40d4: b0 12 fa 6f call __divmodhi4 ;#0x6ffa
40d8: 0f 4c mov r12, r15 ;
printf("prr: %u\n", prr);
40da: 7f f3 and.b #-1, r15 ;r3 As==11
40dc: 0f 12 push r15 ;
40de: 30 12 c0 40 push #16576 ;#0x40c0
40e2: b0 12 9c 67 call printf ;#0x679c
40e6: 21 52 add #4, r1 ;r2 As==10
Como podemos ver, o compilador calcula diretamente o resultado de 255 * 3 a -3 (0xFFFD). E aqui está o problema. De alguma forma, a 255 é interpretado como -1 sinal de 8 bits em vez de 255 sem sinal de 16 bits. Ou ele é analisado para 8 bits em primeiro lugar e, em seguida, assinar estendido para 16 bits. ou o que quer.
A discussão sobre este tema foi iniciado na lista mspgcc discussão já.
Eu não tenho certeza por isso que a definição não funciona, mas você pode estar executando em sobreposições com as variáveis ??uint8_t
. 255 é o valor máximo para uint8_t (2^8 - 1)
, por isso, se você multiplicar isso por 3, você é obrigado a funcionar em alguns problemas de sobreposição sutil.
O compilador pode ser otimizar seu código, e pré-cálculo do resultado da sua expressão matemática e empurrando o resultado em PRR (desde que ele se encaixa, mesmo que o valor intermediário não se encaixa).
Verifique o que acontece se você quebrar a sua expressão como esta (isto não se comportará como o que você quer):
prr = c * a; // rollover!
prr = prr / b;
Você pode precisar usar apenas um tipo de dados maior.
Uma diferença que eu posso pensar no caso-1 é,
O valor literal PRR_SCALE pode entrar em ROM ou código de área. E pode haver alguma diferença na opecode MUL para dizer,
case-1: [register], [rom]
case -2: [register], [register]
Pode não fazer sentido.