Unterschied zwischen uint8_t, uint_fast8_t und uint_least8_t

75

Der C99-Standard führt die folgenden Datentypen ein. Die Dokumentation zur AVR-Standardbibliothek finden Sie hier .

  • uint8_t bedeutet, dass es sich um einen 8-Bit-Typ ohne Vorzeichen handelt.
  • uint_fast8_t bedeutet, dass es das schnellste int ohne Vorzeichen mit mindestens 8 Bit ist.
  • uint_least8_t bedeutet, dass es sich um ein Int ohne Vorzeichen mit mindestens 8 Bits handelt.

Ich verstehe uint8_tund was ist uint_fast8_t(ich weiß nicht, wie es auf Registerebene implementiert ist).

1.Können Sie erklären, was die Bedeutung von "es ist eine unsigned intmit mindestens 8 Bits" ist?

2.Wie uint_fast8_tund uint_least8_thelfen Sie, die Effizienz / den Code-Speicherplatz im Vergleich zum uint8_t?

mic
quelle
Bei Ihrer ersten Frage kann ich mir vorstellen, dass uint8_t8 Bits uint_fast8_tgarantiert> = 8 Bits sind, ähnlich wie bei unsigned char.
Jacob
1
Eine Überlegung ist, dass uint8_tes auf Systemen ohne nativen 8-Bit-Typ keine gibt. Die anderen beiden werden da sein.
Pete Becker
9
Sie haben Antworten erhalten, die sich auf "obskure" und "exotische" Architekturen beziehen. Diese Begriffe sind etwas voreingenommen. Sicher, wenn Sie nur Erfahrung mit Desktop-Systemen haben, liegen diese Architekturen außerhalb Ihres Erfahrungsbereichs. Aber "das habe ich noch nie gesehen" ist nicht dasselbe wie "das ist dunkel oder exotisch". Für Leute, die mit eingebetteten Systemen oder DSPs arbeiten, sind diese Dinge ziemlich häufig.
Pete Becker

Antworten:

98

uint_least8_tist der kleinste Typ mit mindestens 8 Bits. uint_fast8_tist der schnellste Typ mit mindestens 8 Bit.

Sie können die Unterschiede erkennen, indem Sie sich exotische Architekturen vorstellen. Stellen Sie sich eine 20-Bit-Architektur vor. Es unsigned inthat 20 Bits (ein Register) und unsigned char10 Bits. Die sizeof(int) == 2Verwendung von charTypen erfordert jedoch zusätzliche Anweisungen, um die Register zu halbieren. Dann:

  • uint8_t: ist undefiniert (kein 8-Bit-Typ).
  • uint_least8_t: ist unsigned charder kleinste Typ mit mindestens 8 Bit.
  • uint_fast8_t: ist unsigned int, weil in meiner imaginären Architektur eine Halbregistervariable langsamer ist als eine Vollregistervariable.
Rodrigo
quelle
11
Ich finde es toll, wie man sich exotische Architekturen vorstellen muss, um einen Anwendungsfall dafür zu finden. Haben sie in der Praxis einen Nutzen gefunden?
user541686
18
@Mehrdad Wenn Sie beispielsweise in ARM int_fast8_teine 32-Bit-Variable haben, müssen Sie vor arithmetrischen Operationen keine Vorzeichenerweiterung durchführen.
user694733
1
@Mehrdad MIPS zum Beispiel wäre es sehr falsch, uintX_fast_tweniger als 32 Bit zu machen . Sie müssen sich nicht einmal Architekturen vorstellen, um uint8_tundefiniert zu werden. Nehmen Sie zum Beispiel UNIVAC, das 36-Bit ist. Ich würde annehmen, dass es char9-Bit gibt.
Skyking
7
@Mehrdad: Ich gebe zu, dass ich das noch nie gesehen uint_leastX_toder uint_fastX_tin realen Anwendungen verwendet habe. uintX_tJa, sie werden stark genutzt. Es sieht so aus, als wären Menschen nicht sehr interessant für die Portabilität zu exotischen Architekturen. Was erwartet wird, selbst wenn Sie Ihre Nicht-Unterschriften richtig machen, wird Ihr Programm bei tausend verschiedenen Dingen fehlschlagen.
Rodrigo
3
@skyking: Ich sage nicht, dass sie nicht verwendet werden sollten, nur dass sie in der Praxis nicht sehr häufig verwendet werden. Wenn Sie eine reale Anwendung oder Bibliothek finden, die sie sinnvoll verwendet, posten Sie einen Link, da ich keine finden konnte.
Rodrigo
29

uint8_t bedeutet: gib mir ein vorzeichenloses int von genau 8 bit.

uint_least8_tbedeutet: gib mir den kleinsten Typ von Int ohne Vorzeichen, der mindestens 8 Bits hat. Optimieren Sie für den Speicherverbrauch.

uint_fast8_tbedeutet: gib mir ein vorzeichenloses int von mindestens 8 bit. Wählen Sie einen größeren Typ, wenn mein Programm dadurch aufgrund von Überlegungen zur Ausrichtung schneller wird. Geschwindigkeit optimieren.

Im Gegensatz zu den einfachen intTypen ist die signierte Version der oben genannten stdint.h-Typen garantiert das Komplement-Format von 2.

Lundin
quelle
1
Vielen Dank. Gut zu wissen, dass die angemeldeten Typen stdint.hgarantiert zwei ergänzen. Ich frage mich, wo es beim Schreiben von tragbarem Code helfen wird.
Legends2k
6
Beachten Sie, dass nur die genauen Breitenvarianten erforderlich sind, um das Komplementformat von 2 zu verwenden. Beachten Sie auch, dass diese nicht vorhanden sein müssen. Folglich ist keine Plattform erforderlich, um das Komplementformat von 2 zu unterstützen.
Skyking
@ legends2k: Die eingegebenen Typen stdint.hsind weniger hilfreich, als man vielleicht möchte, wenn man versucht, tragbaren Code zu schreiben, da sie zwar das Speicherformat mit zwei Komplementen verwenden müssen, dies jedoch nicht bedeutet, dass sie ein Zweierkomplement-Umhüllungsverhalten aufweisen. Beachten Sie auch, dass selbst auf Plattformen mit int32 Bit das Schreiben eines Werts mit einem int32_t*und das Lesen mit einem int*oder umgekehrt nicht garantiert funktioniert.
Supercat
@supercat Jeder Compiler, den ich gesehen habe, verwendet ein internes typedef für die Typen stdint.h, um sie zu einem Synonym für einen der grundlegenden Integer-Typen "keyword" zu machen. Wenn Sie sich also Gedanken über das Aliasing von Zeigern machen, denke ich nicht, dass dies in der Praxis ein Problem sein wird, nur in der Theorie.
Lundin
@Lundin: Einige Compiler verwenden "long" als typedef für int32_t, andere "int". Selbst wenn "int" und "long" dieselbe Darstellung haben, können (und werden sie manchmal) für die Zwecke der Aliasing-Regeln von C als unterschiedlich angesehen.
Supercat
26

Die Theorie geht ungefähr so:

uint8_tmuss genau 8 Bit betragen, muss aber nicht vorhanden sein. Sie sollten es daher verwenden, wenn Sie sich auf das Modulo-256-Zuweisungsverhalten * einer 8-Bit-Ganzzahl verlassen und wenn Sie einen Kompilierungsfehler bevorzugen, um sich auf obskuren Architekturen schlecht zu verhalten.

uint_least8_tmuss der kleinste verfügbare vorzeichenlose Integer-Typ sein, der mindestens 8 Bit speichern kann. Sie würden es verwenden, wenn Sie den Speicherbedarf von Dingen wie großen Arrays minimieren möchten.

uint_fast8_tsoll der "schnellste" vorzeichenlose Typ sein, der mindestens 8 Bits speichern kann; Es ist jedoch nicht garantiert, dass es für eine bestimmte Operation auf einem bestimmten Prozessor die schnellste ist. Sie würden es bei der Verarbeitung von Code verwenden, der viele Operationen an dem Wert ausführt.

Die Praxis ist, dass die Typen "schnell" und "am wenigsten" nicht viel verwendet werden.

Die "kleinsten" Typen sind nur dann wirklich nützlich, wenn Sie sich für die Portabilität interessieren, um Architekturen mit CHAR_BIT! = 8 zu verschleiern, was die meisten Leute nicht tun.

Das Problem bei den "schnellen" Typen ist, dass "schnellste" schwer zu bestimmen sind. Ein kleinerer Typ kann eine geringere Belastung des Speicher- / Cache-Systems bedeuten, die Verwendung eines Typs, der kleiner als der native ist, erfordert jedoch möglicherweise zusätzliche Anweisungen. Darüber hinaus kann sich das Beste zwischen den Architekturversionen ändern, aber Implementierer möchten in solchen Fällen häufig vermeiden, dass ABI beschädigt wird.

Aus einigen gängigen Implementierungen geht hervor, dass die Definitionen von uint_fastn_t ziemlich willkürlich sind. glibc scheint sie als mindestens die "native Wortgröße" des fraglichen Systems zu definieren, ohne die Tatsache zu berücksichtigen, dass viele moderne Prozessoren (insbesondere 64-Bit-Prozessoren) eine spezifische Unterstützung für schnelle Operationen an Elementen haben, die kleiner als ihr natives Wort sind Größe. IOS definiert sie anscheinend als äquivalent zu den Typen mit fester Größe. Andere Plattformen können variieren.

Alles in allem sollten Sie, wenn die Leistung von straffem Code mit winzigen Ganzzahlen Ihr Ziel ist, Ihren Code auf den Plattformen, die Sie interessieren, mit unterschiedlich großen Typen vergleichen, um herauszufinden, was am besten funktioniert.

* Beachten Sie, dass das Zuweisungsverhalten von Modulo-256 leider nicht immer eine Modulo-256-Arithmetik impliziert, da C eine Fehlfunktion für die Ganzzahl-Promotion aufweist.

Plugwash
quelle
2
Die Definitionen von glibc wurden zu einem Zeitpunkt ausgewählt, als diese Optimierungen nicht existierten. Sie sind jetzt in den ABI integriert und können nicht geändert werden. Dies ist einer der verschiedenen Gründe, warum die Typen _least und _fast in der Praxis nicht wirklich nützlich sind.
zwol
4
@zwol: Ich wünschte, die Sprache würde Typtypen hinzufügen, die in Bezug auf Layout und semantische Anforderungen definiert wurden, z. B. "Ich brauche etwas, dessen untere Bits andere 16-Bit-Typen aliasen und das Werte 0-65535 enthalten kann, aber ich mache keine Es ist nicht erforderlich, größere Werte an diesen Bereich zu binden. " Aliasing, Layout, Bereich und Verhalten außerhalb des Bereichs sollten vier separate Aspekte eines Typs sein, aber C erlaubt nur bestimmte Kombinationen, die zwischen verschiedenen Plattformen nicht konsistent sind.
Supercat
5

Einige Prozessoren können bei kleineren Datentypen nicht so effizient arbeiten wie bei großen. Zum Beispiel gegeben:

uint32_t foo(uint32_t x, uint8_t y)
{
  x+=y;
  y+=2;
  x+=y;
  y+=4;
  x+=y;
  y+=6;
  x+=y;
  return x;
}

wenn ywaren uint32_tein Compiler für die ARM Cortex-M3 könnte einfach erzeugen

add r0,r0,r1,asl #2   ; x+=(y<<2)
add r0,r0,#12         ; x+=12
bx  lr                ; return x

aber da yist uint8_tder compiler müsste stattdessen generieren:

add r0,r0,r1          ; x+=y
add r1,r1,#2          ; Compute y+2
and r1,r1,#255        ; y=(y+2) & 255
add r0,r0,r1          ; x+=y
add r1,r1,#4          ; Compute y+4
and r1,r1,#255        ; y=(y+4) & 255
add r0,r0,r1          ; x+=y
add r1,r1,#6          ; Compute y+6
and r1,r1,#255        ; y=(y+6) & 255
add r0,r0,r1          ; x+=y
bx  lr                ; return x

Der beabsichtigte Zweck der "schnellen" Typen bestand darin, Compilern zu ermöglichen, kleinere Typen, die nicht effizient verarbeitet werden konnten, durch schnellere zu ersetzen. Leider ist die Semantik "schneller" Typen eher schlecht spezifiziert, was wiederum trübe Fragen darüber aufwirft, ob Ausdrücke mit vorzeichenbehafteter oder vorzeichenloser Mathematik bewertet werden.

Superkatze
quelle
Die zusätzlichen potenziell unnötigen Anweisungen beim Umgang mit einem kleineren Datentyp im Vergleich zu einem größeren Datentyp der nativen Wortgröße veranschaulichen deutlich, warum viele der "schnellen" Datentypen möglicherweise eine größere Bitbreite als erwartet haben. Vielen Dank für Ihr Beispiel.
Galaxy
@Galaxy: Leider erlaubt der Standard nicht die Möglichkeit von "kleinsten" Typen, deren Verhalten je nach Kontext variieren kann. Auf vielen Computern kann beispielsweise die Arithmetik für 32-Bit-Werte in Registern schneller sein als Operationen mit 8-Bit-Werten in Registern, aber 8-Bit-Ladevorgänge und -Speicher entsprechen der Geschwindigkeit von 32-Bit-Ladevorgängen und -Speichern sowie dem Caching Probleme können dazu führen, dass 8-Bit-Werte effizienter sind.
Supercat
4

1.Können Sie erklären, was die Bedeutung von "Es ist ein Int ohne Vorzeichen mit mindestens 8 Bits" bedeutet.

Das sollte offensichtlich sein. Dies bedeutet, dass es sich um einen vorzeichenlosen Integer-Typ handelt und dass seine Breite mindestens 8 Bit beträgt. Tatsächlich bedeutet dies, dass es mindestens die Zahlen 0 bis 255 enthalten kann, und es kann definitiv keine negativen Zahlen enthalten, aber es kann möglicherweise Zahlen über 255 enthalten.

Offensichtlich sollten Sie keinen dieser Typen verwenden, wenn Sie eine Zahl außerhalb des Bereichs von 0 bis 255 speichern möchten (und möchten, dass diese tragbar ist).

2.Wie helfen uint_fast8_t und uint_least8_t dabei, die Effizienz / den Codebereich im Vergleich zu uint8_t zu erhöhen?

uint_fast8_tmuss schneller sein, daher sollten Sie dies verwenden, wenn der Code schnell sein soll. uint_least8_tAuf der anderen Seite ist es erforderlich, dass es keinen Kandidaten mit geringerer Größe gibt. Sie würden dies also verwenden, wenn die Größe das Problem ist.


Und natürlich verwenden Sie nur, uint8_twenn Sie unbedingt genau 8 Bit benötigen. Durch uint8_tdie Verwendung wird der Code möglicherweise nicht portierbar, da er uint8_tnicht vorhanden sein muss (da auf bestimmten Plattformen ein derart kleiner Integer-Typ nicht vorhanden ist).

Himmel König
quelle
3

Die "schnellen" Ganzzahltypen werden als die schnellste verfügbare Ganzzahl mit mindestens der erforderlichen Anzahl von Bits definiert (in Ihrem Fall 8).

Eine Plattform kann festlegen , uint_fast8_twie uint8_tdann gibt es absolut keinen Unterschied in der Geschwindigkeit.

Der Grund ist, dass es Plattformen gibt, die langsamer sind, wenn sie ihre native Wortlänge nicht verwenden.

LPs
quelle