Promozione numero intero su MCU a 8 bit


14

Usando avr-gcc come esempio, si specifica che i tipi int hanno una larghezza di 16 bit. L'esecuzione di operazioni su operandi a 8 bit in C comporta la conversione di tali operandi in tipi int a 16 bit a causa della promozione di numeri interi in C. Questo significa che tutte le operazioni aritmetiche a 8 bit su un AVR impiegheranno molto più tempo se scritte in C di se scritto in assemblea a causa della promozione intera di C?


1
Non credo, il compilatore si renderà conto che la variabile di destinazione è un carattere (senza segno), quindi non si preoccuperà di calcolare gli 8 bit principali. Tuttavia, ho scoperto che a volte GCC non è così buono nell'ottimizzazione del codice, quindi se si codifica in ASM, il risultato MGIHT sarà più veloce. Tuttavia, a meno che tu non stia eseguendo attività / interruzioni molto critiche in termini di tempo, con un vincolo di budget molto forte, allora dovresti scegliere un processore più potente e programmarlo in C, o semplicemente non preoccuparti delle prestazioni inferiori (considera invece il tempo sul mercato, migliore leggibilità / riusabilità del codice, meno bug, ecc.).
prossimo hack del

Mi scuso per non aver avuto il tempo di controllare. Tuttavia, penso che ci fosse un flag da riga di comando in gcc che controllasse la "promozione di numeri interi". Forse c'è persino un pragma per controllarlo per specifici pezzi di codice. Quanto è fondamentale la prestazione? In molti usi di un AVR, la differenza di velocità per alcuni aritmetici non è un problema. Foxus su come far funzionare correttamente il codice prima. Quindi se c'è un problema di prestazioni, scopri di cosa si tratta. Sarebbe facile perdere tempo a scrivere codice nell'assemblatore, solo che scopri che non importava.
Bagliore

1
basta smontare e vedere cosa sta facendo il compilatore. Da una prospettiva puramente linguistica sì. l'implementazione qui è atipica. normalmente int tenta di allinearsi con la dimensione del registro, e se hai registri a 16 bit allora la matematica a 8 bit è in realtà più economica a 16 bit di 8. Ma questo è il contrario e con un mcu a 8 bit ha senso implementare int come 16 bit. quindi dovresti probabilmente usare uchar dove tieni a questo, ma non prendere l'abitudine di programmazione comune perché ti fa più male dappertutto.
old_timer

3
Ricorda: evitare di rispondere alle domande nei commenti.
pipe

4
È meglio porre questo tipo di domande agli esperti C di SO, dato che si tratta di una pura domanda software. La promozione di interi in C è un argomento alquanto complesso: il programmatore C medio avrà molte idee sbagliate al riguardo.
Lundin,

Risposte:


16

Per farla breve:

La promozione di interi a 16 bit ha sempre luogo: lo standard C lo impone. Ma al compilatore è consentito ottimizzare il calcolo fino a 8 bit (i compilatori di sistemi integrati sono generalmente abbastanza bravi in ​​tali ottimizzazioni), se si può dedurre che il segno sarà lo stesso di quello che sarebbe stato se il tipo fosse stato promosso.

Questo non è sempre il caso! Le modifiche implicite alla firma causate dalla promozione di numeri interi sono una fonte comune di bug nei sistemi incorporati.

Una spiegazione dettagliata è disponibile qui: Regole di promozione del tipo implicito .


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);
}

come previsto, fun1 è tutto ints, così come la matematica a 16 bit

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

Anche se tecnicamente errato in quanto è un'aggiunta a 16 bit richiamata dal codice, anche non ottimizzato questo compilatore ha rimosso l'adc a causa delle dimensioni del risultato.

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

non sono davvero sorpreso che la promozione avvenga, i compilatori non erano soliti fare questo non sono sicuri di quale versione abbia avuto inizio, si sono imbattuti così presto nella mia carriera e nonostante i compilatori promuovessero fuori servizio (proprio come sopra), facendo la promozione anche se io gli disse di fare matematica uchar, non sorpreso.

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 l'ideale, so che è a 8 bit, voglio un risultato a 8 bit, quindi ho semplicemente detto di fare 8 bit fino in fondo.

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

Quindi, in generale, è meglio puntare alla dimensione del registro, che è idealmente la dimensione di un (u) int, per un mcu a 8 bit come questo gli autori del compilatore hanno dovuto scendere a compromessi ... Il fatto di non prendere l'abitudine di usare uchar per la matematica che sai non ha bisogno di più di 8 bit come quando sposti quel codice o scrivi un nuovo codice come quello su un processore con registri più grandi ora il compilatore deve iniziare a mascherare e firmare l'estensione, cosa che alcuni fanno in modo nativo in alcune istruzioni, e altri no.

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

forzare 8 bit costa di più. Ho imbrogliato un po '/ molto, avrei bisogno di esempi leggermente più complicati per vedere di più in modo equo.

EDIT basato sulla discussione dei commenti

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

nessuna sorpresa. Sebbene perché l'ottimizzatore abbia lasciato quell'istruzione extra, non puoi usare ldi su r19? (Conoscevo la risposta quando l'ho chiesto).

EDIT2

per 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.

per evitare la cattiva abitudine o meno il confronto a 8 bit

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.

chiaramente l'ottimizzazione era attiva solo un secondo per provare con il tuo compilatore per vedere come si confronta con il mio output, ma comunque:

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

E sì, l'uso di byte per variabili di dimensioni in byte, certamente su un avr, una foto, ecc., Ti farà risparmiare memoria e vuoi davvero provare a conservarla ... se la stai effettivamente usando, ma come mostrato qui il meno possibile è sarà nella memoria, il più possibile nei registri, quindi il risparmio di flash deriva dal fatto di non avere variabili extra, il risparmio di RAM può essere o non essere reale.


2
"i compilatori non erano abituati a fare questo, non erano sicuri di quale versione facesse accadere questo inizio, si sono imbattuti in questo all'inizio della mia carriera e nonostante i compilatori promuovessero fuori servizio (proprio come sopra), facendo la promozione anche se gli ho detto di fare matematica uchar, non sorpreso." È perché i compilatori C di sistemi embedded avevano una orribile conformità standard :) Il compilatore di solito è autorizzato a ottimizzare, ma qui non può dedurre che il risultato si adatterà in un unsigned charmodo quindi deve eseguire la promozione a 16 bit, come richiesto dallo standard.
Lundin,

1
@old_timer (a<<8)|bè sempre sbagliato per qualsiasi sistema con int16 bit. averrà implicitamente promosso a intcui è firmato. Nel caso in cui acontenesse un valore nel MSB, si finisce per spostare quei dati nel bit di segno di un numero a 16 bit, il che invoca un comportamento indefinito.
Lundin,

1
fun3 is fun..ny ... totalmente non ottimizzato dal compilatore ... Considerato che r1 è sempre 0 in GCC e indica ra, rb, {rh, rl} i registri per le variabili a, b e il risultato, il compilatore avrebbe potuto fare: 1) mov rh, r1; 2) mov rl, ra; 2) aggiungi rl, rb; 3) adc rh, rh; 4) ret. 4 istruzioni, vs 7 o 8 ... L'istruzione 1 può essere modificata in ldi rh, 0.
prossimo hack del

1
Questa sarebbe una risposta migliore se specificasse il compilatore e le relative opzioni in uso.
Russell Borogove,

1
È una buona idea evitare di usare int / char ecc. E invece usare int16_t e int8_t molto più espliciti e leggibili.
utente

7

Non necessariamente, poiché i compilatori moderni fanno un buon lavoro nell'ottimizzare il codice generato. Ad esempio, se scrivi z = x + y;dove sono tutte le variabili unsigned char, il compilatore deve promuoverle unsigned intprima di eseguire i calcoli. Tuttavia, poiché il risultato finale sarà esattamente lo stesso senza la promozione, il compilatore genererà il codice che aggiunge solo variabili a 8 bit.

Naturalmente, questo non è sempre il caso, ad esempio il risultato di z = (x + y)/2;dipenderebbe dal byte superiore, quindi la promozione avrà luogo. Può ancora essere evitato senza ricorrere all'assemblaggio riportando il risultato intermedio a unsigned char.

Alcune di tali inefficienze possono essere evitate utilizzando le opzioni del compilatore. Ad esempio, molti compilatori a 8 bit hanno un pragma o un'opzione della riga di comando per adattarsi ai tipi di enumerazione in 1 byte, anziché intcome richiesto da C.


4
"il compilatore è richiesto per promuoverli a unsigned int". No, il compilatore deve promuoverlo int, poiché charmolto probabilmente non avrà lo stesso rango di conversione di intqualsiasi piattaforma.
Lundin,

3
"Ad esempio, molti compilatori a 8 bit hanno un pragma o un'opzione della riga di comando per adattarsi ai tipi di enumerazione in 1 byte, anziché int come richiesto da C." Lo standard C consente di allocare le variabili di enumerazione in 1 byte. Richiede solo che le costanti di enumerazione debbano essere int(sì, è incoerente). 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,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.