C Integer Promotion auf 8-Bit-MCUs

14

Am Beispiel von avr-gcc wird für int-Typen eine Breite von 16 Bit angegeben. Das Ausführen von Operationen mit 8-Bit-Operanden in C führt dazu, dass diese Operanden aufgrund der Ganzzahl-Heraufstufung in C in 16-Bit-Int-Typen konvertiert werden. Bedeutet dies, dass alle 8-Bit-Arithmetikoperationen auf einem AVR viel länger dauern, wenn sie in C geschrieben werden Wenn in Assembly aufgrund der ganzzahligen Promotion von C geschrieben?

pr871
quelle
1
Ich denke nicht, der Compiler wird erkennen, dass die Zielvariable ein (vorzeichenloses) Zeichen ist, daher wird es nicht die Mühe machen, die oberen 8 Bits zu berechnen. Dennoch stellte ich fest, dass GCC manchmal nicht so gut darin ist, Code zu optimieren. Wenn Sie also in ASM codieren, ist das Ergebnis MGIHT schneller. Wenn Sie jedoch nicht sehr zeitkritische Aufgaben / Interrutps mit sehr hohen Budgetbeschränkungen ausführen, sollten Sie entweder einen leistungsstärkeren Prozessor auswählen und ihn in C programmieren oder sich keine Gedanken über die geringere Leistung machen (stattdessen Zeit in Betracht ziehen -to-market, bessere Lesbarkeit / Wiederverwendbarkeit von Code, weniger Bugs, ecc ..).
Next-Hack
Ich entschuldige mich dafür, dass ich keine Zeit habe, das zu überprüfen. Ich denke jedoch, dass es ein Befehlszeilen-Flag für gcc gab, das die 'Ganzzahl-Heraufstufung' steuern würde. Es gibt vielleicht sogar ein Pragma, um es für bestimmte Teile des Codes zu steuern. Wie kritisch ist die Leistung? Bei vielen Anwendungen eines AVR ist der Geschwindigkeitsunterschied für einige Berechnungen kein Problem. Foxus darüber, ob der Code zuerst richtig funktioniert. Wenn es dann ein Leistungsproblem gibt, finden Sie heraus, was es ist. Es wäre einfach, Zeit mit dem Programmieren in Assembler zu verschwenden, aber Sie werden feststellen, dass es keine Rolle spielt.
gbulmer
1
Zerlegen Sie einfach und sehen Sie, was der Compiler tut. Aus rein sprachlicher Sicht ja. Die Umsetzung hier ist untypisch. Normalerweise versucht sich int an der Registergröße auszurichten, und wenn Sie 16-Bit-Register hatten, ist 8-Bit-Mathematik bei 16-Bit-Registern günstiger als 8. Dies ist jedoch umgekehrt, und mit einer 8-Bit-MCU ist es sinnvoll, int zu implementieren als 16 bit. Sie sollten uchar also wahrscheinlich verwenden, wenn Sie sich dafür interessieren, aber machen Sie das nicht zu einer allgemeinen Programmiergewohnheit, da es Ihnen sonst überall am meisten weh tut.
old_timer
3
Denken Sie
Pipe
4
Diese Art von Fragen sollten Sie besser den C-Experten von SO stellen, da es sich um eine reine Software-Frage handelt. Integer-Promotion in C ist ein ziemlich komplexes Thema - der durchschnittliche C-Programmierer wird viele Missverständnisse darüber haben.
Lundin

Antworten:

16

Um es kurz zu machen:

Die ganzzahlige Heraufstufung auf 16 Bit findet immer statt - der C-Standard erzwingt dies. Der Compiler kann die Berechnung jedoch wieder auf 8 Bit optimieren (eingebettete System-Compiler sind normalerweise bei solchen Optimierungen ziemlich gut), wenn sich daraus ergibt, dass das Vorzeichen das gleiche ist, wie es gewesen wäre, wenn der Typ heraufgestuft worden wäre.

Dies ist nicht immer der Fall! Implizite Signifikanzänderungen, die durch die Heraufstufung von Ganzzahlen verursacht werden, sind eine häufige Fehlerquelle in eingebetteten Systemen.

Eine ausführliche Erklärung finden Sie hier: Regeln für die implizite Typheraufstufung .

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

Wie erwartet ist fun1 alles in allem, ebenso die 16-Bit-Mathematik

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

Obwohl technisch nicht korrekt, da es sich um eine 16-Bit-Addition handelt, die vom Code aufgerufen wird, entfernte dieser Compiler den ADC aufgrund der Ergebnisgröße, auch wenn er nicht optimiert war.

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

Nicht wirklich überrascht, dass hier die Promotion stattfindet. Früher haben Compiler dies nicht getan. Sie waren sich nicht sicher, in welcher Version dieser Start stattgefunden hat. Sie sind zu Beginn meiner Karriere darauf gestoßen, und obwohl die Compiler nicht in der richtigen Reihenfolge promoten (genau wie oben), haben sie die Promotion durchgeführt, obwohl ich sagte ihm, er solle uchar rechnen, nicht überrascht.

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

und das Ideal, ich weiß, es ist 8-Bit, möchte ein 8-Bit-Ergebnis, also habe ich ihm einfach gesagt, dass er 8-Bit durchziehen soll.

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

Im Allgemeinen ist es also besser, die Registergröße anzustreben, die idealerweise der Größe eines (u) int entspricht. Für eine 8-Bit-CPU wie diese mussten die Compiler-Autoren einen Kompromiss eingehen Wenn Sie uchar für Mathematik verwenden, von dem Sie wissen, dass es nicht mehr als 8 Bit benötigt, als wenn Sie diesen Code verschieben oder neuen Code wie diesen auf einen Prozessor mit größeren Registern schreiben, muss der Compiler jetzt mit dem Maskieren und Erweitern von Zeichen beginnen, was einige in einigen Anweisungen nativ tun. und andere nicht.

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

8 Bit zu erzwingen kostet mehr. Ich habe ein bisschen / viel geschummelt, würde etwas kompliziertere Beispiele brauchen, um mehr davon auf faire Weise zu sehen.

BEARBEITEN basierend auf Kommentardiskussion

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

keine Überraschung. Obwohl der Optimierer diese zusätzliche Anweisung verlassen hat, können Sie ldi auf r19 nicht verwenden? (Ich wusste die Antwort, als ich sie fragte).

EDIT2

für 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.

um die schlechte Angewohnheit oder nicht 8-Bit-Vergleich zu vermeiden

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.

Offensichtlich dauerte die Optimierung nur eine Sekunde, um mit Ihrem eigenen Compiler zu versuchen, zu sehen, wie sie mit meiner Ausgabe verglichen werden kann, aber trotzdem:

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

Und ja, die Verwendung von Bytes für Variablen mit Byte-Größe, sicherlich auf einem AVR, einem Bild usw., spart Speicher und Sie möchten wirklich versuchen, ihn zu erhalten ... wenn Sie ihn tatsächlich verwenden, aber wie hier gezeigt, ist dies so wenig wie möglich Wird im Speicher gespeichert, so viel wie möglich in den Registern, so dass die Flash-Einsparungen dadurch erzielt werden, dass keine zusätzlichen Variablen vorhanden sind. Die RAM-Einsparungen können real sein oder auch nicht.

Oldtimer
quelle
2
"Früher haben Compiler das nicht getan, nicht sicher, welche Version diesen Start ausgelöst hat, sie sind in meiner Karriere schon so früh darauf gestoßen, und obwohl die Compiler nicht in der richtigen Reihenfolge promoten (wie oben), machten sie die Promo, obwohl ich ihr befohlen hatte, uchar mathe zu machen, nicht überrascht." Es ist , weil eingebettete Systeme C - Compiler verwenden schreckliche Standardkonformität haben :) Der Compiler in der Regel zu optimieren erlaubt, aber hier kann es nicht abziehen , dass das Ergebnis in einem paßt , unsigned chardamit es hat die Förderung auf 16 Bit durchzuführen, je nach Bedarf nach dem Standard.
Lundin
1
@old_timer (a<<8)|bist immer falsch für ein System mit int16 Bit. awird implizit befördert, zu intdem signiert wird. Falls aein Wert im MSB enthalten ist, verschieben Sie diese Daten letztendlich in das Vorzeichenbit einer 16-Bit-Zahl, wodurch undefiniertes Verhalten ausgelöst wird.
Lundin
1
fun3 is fun..ny ... vom Compiler völlig unoptimiert ... Wenn man bedenkt, dass r1 in GCC immer 0 ist und ra, rb, {rh, rl} die Register für die Variablen a, b und das Ergebnis anzeigt, der Compiler hätte tun können: 1) mov rh, r1; 2) mov rl, ra; 2) füge rl, rb hinzu; 3) adc rh, rh; 4) ret. 4 Anweisungen, vs 7 oder 8 ... Anweisung 1 kann in ldi rh, 0 geändert werden.
Next-Hack
1
Dies wäre eine bessere Antwort, wenn der Compiler und die verwendeten relevanten Optionen angegeben würden.
Russell Borogove
1
Es ist eine gute Idee, die Verwendung von int / char usw. zu vermeiden und stattdessen die viel expliziteren und lesbareren int16_t und int8_t zu verwenden.
Benutzer
7

Nicht unbedingt, da moderne Compiler einen guten Job bei der Optimierung des generierten Codes machen. Wenn Sie beispielsweise schreiben, z = x + y;wo sich alle Variablen befinden unsigned char, muss der Compiler sie heraufstufen, unsigned intbevor Sie die Berechnungen ausführen. Da jedoch das Endergebnis ohne die Heraufstufung genau dasselbe ist, generiert der Compiler Code, der lediglich 8-Bit-Variablen hinzufügt.

Dies ist natürlich nicht immer der Fall, zum Beispiel würde das Ergebnis von z = (x + y)/2;vom oberen Byte abhängen, so dass eine Heraufstufung stattfinden wird. Es kann dennoch vermieden werden, ohne auf die Montage zurückzugreifen, indem das Zwischenergebnis auf zurückgegossen wird unsigned char.

Einige dieser Ineffizienzen können mit Compiler-Optionen vermieden werden. Beispielsweise verfügen viele 8-Bit-Compiler über ein Pragma oder eine Befehlszeilenoption, um die Aufzählungstypen auf 1 Byte anzupassen, anstatt intwie von C gefordert.

Dmitry Grigoryev
quelle
4
msgstr "Der Compiler muss sie auf int ohne Vorzeichen hochstufen". Nein, der Compiler muss sie heraufstufen int, da charsie höchstwahrscheinlich nicht den gleichen Conversion-Rang wie intauf einer Plattform haben.
Lundin
3
"Beispielsweise haben viele 8-Bit-Compiler ein Pragma oder eine Befehlszeilenoption, um Aufzählungstypen in 1 Byte anstelle von int anzupassen, wie dies von C gefordert wird." Der C-Standard erlaubt die Zuweisung von Aufzählungsvariablen in 1 Byte. Es ist nur erforderlich, dass Aufzählungskonstanten vorhanden sein müssen int(ja, es ist inkonsistent). 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