Promoção de número inteiro C em MCUs de 8 bits

14

Usando o avr-gcc como exemplo, os tipos int são especificados para ter 16 bits de largura. Executar operações em operandos de 8 bits em C resulta na conversão desses operandos em tipos int de 16 bits devido à promoção de números inteiros em C. Isso significa que todas as operações aritméticas de 8 bits em um AVR levarão muito mais tempo se forem escritas em C do que se escrito em assembly devido à promoção inteira de C?

pr871
fonte
1
Acho que não, o compilador perceberá que a variável de destino é um caractere (não assinado), portanto não se preocupará em calcular os 8 bits mais importantes. Ainda assim, descobri que o GCC às vezes não é tão bom na otimização de código; portanto, se você codificar no ASM, o resultado MGIHT será mais rápido. No entanto, a menos que você esteja executando tarefas / interrupções muito críticas, com uma restrição orçamentária muito forte, escolha um processador mais poderoso e programe-o em C ou simplesmente não se preocupe com o desempenho mais baixo (considere o tempo - ao mercado, melhor legibilidade / reutilização de código, menos bugs, etc.).
next-hack
Peço desculpas por não ter tido tempo de verificar. No entanto, acho que havia um sinalizador de linha de comando no gcc que controlaria a 'promoção inteira'. Talvez haja até um pragma para controlá-lo para trechos de código específicos. Quão crítico é o desempenho? Em muitos usos de um AVR, a diferença de velocidade para alguma aritmética não é um problema. Foxus em obter o código funcionando corretamente primeiro. Então, se houver um problema de desempenho, descubra qual é. Seria fácil perder tempo codificando em assembler, mas você acha que isso não importa.
gbulmer
1
desmonte e veja o que o compilador está fazendo. De uma perspectiva pura da linguagem, sim. a implementação aqui é atípica. normalmente int tenta se alinhar com o tamanho do registro, e se você tivesse registradores de 16 bits, a matemática de 8 bits é realmente mais barata de 16 bits que 8. Mas esse é o contrário e, com um mcu de 8 bits, faz sentido implementar int como 16 bits. então você provavelmente deve usar o uchar onde se preocupa com isso, mas não faça disso um hábito de programação comum, pois isso o magoa mais em qualquer outro lugar.
old_timer
3
Lembre-se: evite responder perguntas nos comentários.
pipe
4
É melhor perguntar a esse tipo de perguntas aos especialistas em C da SO, pois é uma questão de software puro. A promoção de números inteiros em C é um tópico um pouco complexo - o programador C médio terá muitos conceitos errados sobre isso.
Lundin

Respostas:

16

Longa história curta:

A promoção inteira para 16 bits sempre ocorre - o padrão C impõe isso. Mas é permitido ao compilador otimizar o cálculo de volta para 8 bits (os compiladores de sistemas embarcados geralmente são muito bons em tais otimizações), se for possível deduzir que o sinal será o mesmo que seria se o tipo tivesse sido promovido.

Isso não é sempre o caso! Alterações implícitas de assinatura causadas pela promoção de números inteiros são uma fonte comum de erros em sistemas incorporados.

Explicação detalhada pode ser encontrada aqui: Regras implícitas de promoção de tipo .

Lundin
fonte
8
unsigned int fun1 ( unsigned int a, unsigned int b )
{
    return(a+b);
}

unsigned char fun2 ( unsigned int a, unsigned int b )
{
    return(a+b);
}

unsigned int fun3 ( unsigned char a, unsigned char b )
{
    return(a+b);
}

unsigned char fun4 ( unsigned char a, unsigned char b )
{
    return(a+b);
}

como esperado fun1 é tudo ints o mesmo acontece com a matemática de 16 bits

00000000 <fun1>:
   0:   86 0f           add r24, r22
   2:   97 1f           adc r25, r23
   4:   08 95           ret

Embora tecnicamente incorreto, pois é uma adição de 16 bits chamada pelo código, mesmo não otimizado, esse compilador removeu o adc devido ao tamanho do resultado.

00000006 <fun2>:
   6:   86 0f           add r24, r22
   8:   08 95           ret

Não é surpresa que aqui a promoção aconteça, os compiladores não costumavam fazer isso, não tinham certeza de qual versão fazia com que isso acontecesse, entraram nessa no início da minha carreira e, apesar dos compiladores promoverem fora de ordem (como acima), fazendo a promoção mesmo que eu disse para fazer matemática uchar, não surpreso.

0000000a <fun3>:
   a:   70 e0           ldi r23, 0x00   ; 0
   c:   26 2f           mov r18, r22
   e:   37 2f           mov r19, r23
  10:   28 0f           add r18, r24
  12:   31 1d           adc r19, r1
  14:   82 2f           mov r24, r18
  16:   93 2f           mov r25, r19
  18:   08 95           ret

e o ideal, eu sei que é 8 bits, quero um resultado de 8 bits, então eu simplesmente disse para ele fazer 8 bits por todo o caminho.

0000001a <fun4>:
  1a:   86 0f           add r24, r22
  1c:   08 95           ret

Portanto, em geral, é melhor apontar para o tamanho do registro, que é idealmente o tamanho de um (u) int, para um mcu de 8 bits como este, os autores do compilador tiveram que fazer um compromisso ... Ponto de não criar o hábito de usando uchar para matemática que você sabe que não precisa de mais de 8 bits, como quando você move esse código ou escreve um novo código em um processador com registros maiores agora o compilador precisa começar a mascarar e a estender o sinal, o que alguns são nativos em algumas instruções, e outros não.

00000000 <fun1>:
   0:   e0800001    add r0, r0, r1
   4:   e12fff1e    bx  lr

00000008 <fun2>:
   8:   e0800001    add r0, r0, r1
   c:   e20000ff    and r0, r0, #255    ; 0xff
  10:   e12fff1e    bx  lr

forçar 8 bits custa mais. Eu trapacei um pouco / muito, precisaria de exemplos um pouco mais complicados para ver mais disso de uma maneira justa.

EDIT com base na discussão dos comentários

unsigned int fun ( unsigned char a, unsigned char b )
{
    unsigned int c;
    c = (a<<8)|b;
    return(c);
}

00000000 <fun>:
   0:   70 e0           ldi r23, 0x00   ; 0
   2:   26 2f           mov r18, r22
   4:   37 2f           mov r19, r23
   6:   38 2b           or  r19, r24
   8:   82 2f           mov r24, r18
   a:   93 2f           mov r25, r19
   c:   08 95           ret

00000000 <fun>:
   0:   e1810400    orr r0, r1, r0, lsl #8
   4:   e12fff1e    bx  lr

sem surpresa. Embora por que o otimizador tenha deixado essa instrução extra, você não pode usar o ldi na r19? (Eu sabia a resposta quando perguntei).

EDIT2

para avr

avr-gcc --version
avr-gcc (GCC) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

para evitar o mau hábito ou não comparação de 8 bits

arm-none-eabi-gcc --version
arm-none-eabi-gcc (GCC) 7.2.0
Copyright (C) 2017 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

claramente a otimização estava ligada leva apenas um segundo para tentar com seu próprio compilador para ver como ele se compara à minha saída, mas de qualquer maneira:

whatever-gcc -O2 -c so.c -o so.o
whatever-objdump -D so.o

E sim, o uso de bytes para variáveis ​​de tamanho de bytes, certamente em um avr, pic, etc, economizará memória e você deseja realmente tentar conservá-lo ... se você estiver realmente usando, mas, como mostrado aqui, o mínimo possível é ficará na memória, tanto quanto possível nos registros, de modo que a economia de flash ocorre por não ter variáveis ​​extras, a economia de memória ram pode ou não ser real.

old_timer
fonte
2
"os compiladores não costumavam fazer isso, não tinham certeza de qual versão fazia com que isso acontecesse, entraram nessa no início da minha carreira e, apesar dos compiladores promoverem fora de ordem (como acima), fazendo a promoção mesmo que eu tenha dito para fazer matemática uchar, não surpreso." Isso ocorre porque os compiladores C dos sistemas embarcados costumavam ter uma péssima conformidade padrão :) Geralmente, o compilador pode otimizar, mas aqui não é possível deduzir que o resultado se encaixará em um, unsigned charportanto, ele deve executar a promoção em 16 bits, conforme necessário pelo padrão.
Lundin
1
@old_timer (a<<8)|bestá sempre errado para qualquer sistema com int16 bits. aserá implicitamente promovido para o intqual está assinado. Caso amantenha um valor no MSB, você acaba transferindo esses dados para o bit de sinal de um número de 16 bits, o que invoca um comportamento indefinido.
Lundin
1
fun3 é divertido..ny ... totalmente não otimizado pelo compilador ... Considerando que r1 é sempre 0 no GCC e indica ra, rb, {rh, rl} os registros para as variáveis ​​a, be o resultado, o compilador poderia ter feito: 1) mov rh, r1; 2) mov rl, ra; 2) adicione rl, rb; 3) adc rh, rh; 4) ret. 4 Instruções, vs 7 ou 8 ... A instrução 1 pode ser alterada em ldi rh, 0.
next-hack
1
Essa seria uma resposta melhor se ele especificasse o compilador e as opções relevantes em uso.
Russell Borogove
1
É uma boa idéia evitar o uso de int / char etc. e, em vez disso, usar os int16_t e int8_t muito mais explícitos e legíveis.
usuário
7

Não necessariamente, uma vez que os compiladores modernos fazem um bom trabalho na otimização do código gerado. Por exemplo, se você escrever z = x + y;onde estão todas as variáveis unsigned char, o compilador precisará promovê-las unsigned intantes de executar os cálculos. No entanto, como o resultado final será exatamente o mesmo sem a promoção, o compilador gerará código que apenas adiciona variáveis ​​de 8 bits.

Obviamente, esse nem sempre é o caso, por exemplo, o resultado z = (x + y)/2;dependeria do byte superior, portanto a promoção ocorrerá. Ainda pode ser evitado sem recorrer à montagem lançando o resultado intermediário de volta para unsigned char.

Algumas dessas ineficiências podem ser evitadas usando as opções do compilador. Por exemplo, muitos compiladores de 8 bits têm um pragma ou uma opção de linha de comando para ajustar os tipos de enumeração em 1 byte, em vez de intconforme exigido por C.

Dmitry Grigoryev
fonte
4
"o compilador é necessário para promovê-los a int não assinado". Não, é necessário que o compilador os promova int, pois charprovavelmente não terá a mesma classificação de conversão que intem qualquer plataforma.
Lundin
3
"Por exemplo, muitos compiladores de 8 bits têm um pragma ou uma opção de linha de comando para ajustar os tipos de enumeração em 1 byte, em vez de int conforme exigido por C." O padrão C permite que variáveis ​​de enumeração sejam alocadas em 1 byte. Requer apenas que as constantes de enumeração sejam int(sim, é inconsistente). C11 6.7.2.2Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type. The choice of type is implementation-defined...
Lundin