Wurden Typen mit variabler Breite in modernem C durch feste Typen ersetzt?

21

Ich bin heute in einem Review über Code Review auf einen interessanten Punkt gestoßen . @Veedrac empfahl in dieser Antwort, dass Typen mit variabler Größe (z. B. intund long) durch Typen mit fester Größe wie uint64_tund ersetzt werden uint32_t. Zitat aus den Kommentaren dieser Antwort:

Die Größen von int und long (und damit die Werte, die sie enthalten können) sind plattformabhängig. Auf der anderen Seite ist int32_t immer 32 Bit lang. Die Verwendung von int bedeutet lediglich, dass Ihr Code auf verschiedenen Plattformen unterschiedlich funktioniert. Dies ist im Allgemeinen nicht das, was Sie wollen.

Die Argumentation hinter der Norm, die die gängigen Typen nicht festlegt , wird hier teilweise von @supercat erklärt. C wurde so geschrieben, dass es über Architekturen hinweg portierbar ist, im Gegensatz zu Assemblys, die zu dieser Zeit normalerweise für die Systemprogrammierung verwendet wurden.

Ich denke, die Designabsicht war ursprünglich, dass jeder andere Typ als int das kleinste Ding ist, das Zahlen verschiedener Größen handhaben kann, und dass int die praktischste "Allzweck" -Größe ist, die +/- 32767 handhaben kann.

Was mich angeht, habe ich intdie Alternativen immer benutzt und mich nicht wirklich darum gekümmert. Ich habe immer gedacht, dass es der Typ mit der besten Leistung ist, Ende der Geschichte. Der einzige Ort, an dem ich eine feste Breite für nützlich hielt, ist das Codieren von Daten zum Speichern oder zum Übertragen über ein Netzwerk. Ich habe selten Typen mit fester Breite in Code gesehen, der von anderen geschrieben wurde.

Bin ich in den 70ern stecken geblieben oder gibt es tatsächlich einen Grund für die Verwendung intin der Ära von C99 und darüber hinaus?

jacwah
quelle
1
Ein Teil der Leute ahmt einfach andere nach. Ich glaube, der Großteil des Codes mit festem Bit-Typ wurde gewissenlos gemacht. Kein Grund, die Größe nicht zu ändern. Ich habe Code in erster Linie auf 16-Bit-Plattformen (MS-DOS und Xenix der 80er Jahre) erstellt, die heute nur auf 64 kompiliert und ausgeführt werden und die Vorteile der neuen Wortgröße und Adressierung nutzen, indem sie nur kompiliert werden. Das heißt, die Serialisierung zum Exportieren / Importieren von Daten ist ein sehr wichtiges Architekturdesign, um sie portabel zu halten.
Luciano
1
Siehe auch
dan04

Antworten:

7

Es gibt einen weit verbreiteten und gefährlichen Mythos, der es uint32_tProgrammierern erspart, sich Gedanken über die Größe von zu machen int. Während es hilfreich wäre, wenn das Normungskomitee ein Mittel zur Deklaration von Ganzzahlen mit maschinenunabhängiger Semantik definieren würde, haben vorzeichenlose Typen uint32_teine Semantik, die zu locker ist, um Code sauber und portabel schreiben zu können. Ferner haben signierte Typen wie int32Semantiken, die für viele Anwendungen unnötig genau definiert sind und somit verhindern, dass ansonsten nützliche Optimierungen vorgenommen werden.

Betrachten Sie zum Beispiel:

uint32_t upow(uint32_t n, uint32_t exponent)
{
  while(exponent--)
    n*=n;
  return n;
}

int32_t spow(int32_t n, uint32_t exponent)
{
  while(exponent--)
    n*=n;
  return n;
}

Auf Computern, auf denen int4294967295 oder 18446744065119617025 nicht gespeichert werden können, wird die erste Funktion für alle Werte von nund definiert exponent, und ihr Verhalten wird nicht von der Größe von beeinflusst int. Darüber hinaus verlangt der Standard nicht, dass sich das Verhalten auf Computern mit einer Größe von int einigen Werten von unterscheidet, nund exponentveranlasst ihn jedoch, Undefiniertes Verhalten auf Computern aufzurufen, auf denen 4294967295 darstellbar ist int, 18446744065119617025 jedoch nicht.

Die zweite Funktion liefert ein undefiniertes Verhalten für einige Werte von nund exponentauf Maschinen, auf denen int4611686014132420609 nicht gespeichert werden kann, liefert jedoch ein definiertes Verhalten für alle Werte von nund exponentauf allen Maschinen, auf denen dies möglich ist (die Spezifikationen für int32_timplizieren, dass zwei das Umhüllungsverhalten auf Maschinen ergänzen, auf denen es vorhanden ist) ist kleiner als int).

Historisch gesehen, obwohl der Standard nichts darüber sagte, was Compiler mit intÜberlauf tun sollten upow, hätten Compiler durchweg das gleiche Verhalten ergeben, als ob intsie groß genug gewesen wären , um nicht überzulaufen. Leider versuchen einige neuere Compiler möglicherweise, Programme zu "optimieren", indem sie Verhaltensweisen beseitigen, die nicht durch den Standard vorgeschrieben sind.

Superkatze
quelle
3
Jeder pow, der den Code manuell implementieren möchte , sollte sich daran erinnern, dass dieser Code nur ein Beispiel ist und keine Lösung bietet exponent=0!
Mark Hurd
1
Ich denke, Sie sollten den Präfix-Dekrementierungsoperator verwenden, nicht den Postfix. Derzeit wird 1 zusätzliche Multiplikation ausgeführt. Beispiel: exponent=1n wird einmal mit sich selbst multipliziert, da die Dekrementierung nach der Prüfung durchgeführt wird, wenn die Inkrementierung vor der Prüfung durchgeführt wird. dh --exponent), es wird keine Multiplikation durchgeführt und n selbst wird zurückgegeben.
ALXGTV
2
@MarkHurd: Die Funktion hat einen schlechten Namen, da sie tatsächlich berechnet wird N^(2^exponent). Berechnungen der Form N^(2^exponent)werden jedoch häufig für die Berechnung von Exponentiationsfunktionen verwendet. Die mod-4294967296-Exponentiation ist nützlich, um beispielsweise den Hash der Verkettung zweier Zeichenfolgen zu berechnen , deren Hashes sind bekannt.
Supercat
1
@ALXGTV: Die Funktion sollte veranschaulichen, dass etwas leistungsbezogenes berechnet wurde. Was es tatsächlich berechnet, ist N ^ (2 ^ Exponent), was ein Teil der effizienten Berechnung von N ^ Exponent ist, und es kann gut scheitern, selbst wenn N klein ist (wiederholte Multiplikation von a uint32_tmit 31 wird niemals UB ergeben, aber das effiziente Weg, um 31 ^ N zu berechnen, beinhaltet Berechnungen von 31 ^ (2 ^ N), die werden
Supercat
Ich denke nicht, dass dies ein gutes Argument ist. Das Ziel ist nicht, Funktionen für alle Eingaben zu definieren, ob sinnvoll oder nicht; es ist in der Lage zu sein, über Größen und Überlauf nachzudenken. int32_tManchmal haben Sie einen Überlauf definiert und manchmal nicht, was Sie anscheinend zu erwähnen scheinen. Dies scheint von untergeordneter Bedeutung zu sein, um überhaupt einen Überlauf zu verhindern. Und wenn Sie einen definierten Überlauf wünschen, möchten Sie das Ergebnis wahrscheinlich modulo mit einem festen Wert versehen - also verwenden Sie trotzdem Typen mit fester Breite.
Veedrac
4

Für Werte, die eng mit Zeigern (und damit der Größe des adressierbaren Speichers) zusammenhängen, wie z. B. Puffergrößen, Array-Indizes und Windows lParam, ist ein Integer-Typ mit einer architekturabhängigen Größe sinnvoll. Daher sind Typen mit variabler Größe immer noch nützlich. Aus diesem Grund wir die typedefs haben size_t, ptrdiff_t, intptr_tusw. Sie haben typedefs sein , weil keine der eingebauten in C Integer - Typen Zeiger-Größe sein muss.

So ist die Frage wirklich , ob ist char, short, int, long, und long longsind immer noch nützlich.

IME ist es immer noch üblich, dass C- und C ++ - Programme intfür die meisten Dinge verwendet werden. Und meistens (dh wenn Ihre Zahlen im Bereich von ± 32.767 liegen und Sie keine strengen Leistungsanforderungen haben) funktioniert dies einwandfrei.

Aber was ist, wenn Sie mit Zahlen im 17-32-Bit-Bereich arbeiten müssen (wie die Bevölkerung von Großstädten)? Sie könnten es verwenden int, aber das wäre eine harte Kodierung einer Plattformabhängigkeit. Wenn Sie sich strikt an den Standard halten möchten, können Sie ihn verwenden long, der garantiert mindestens 32 Bit beträgt.

Das Problem ist, dass der C-Standard keine maximale Größe für einen Integer-Typ festlegt. Es gibt Implementierungen mit long64 Bit, wodurch sich die Speichernutzung verdoppelt. Und wenn dies longzufällig Elemente eines Arrays mit Millionen von Elementen sind, wird die Erinnerung wie durchgeknallt.

Also, weder intnoch longist eine geeignete Art hier zu verwenden , wenn Sie Ihr Programm soll sowohl plattformübergreifende und speichereffizient sein. Eintreten int_least32_t.

  • Ihr I16L32-Compiler bietet Ihnen eine 32-Bit-Version long, wodurch die Kürzungsprobleme von vermieden werdenint
  • Mit Ihrem I32L64-Compiler erhalten Sie 32-Bit-Dateien int, wodurch die Verschwendung von 64-Bit-Speicher vermieden wird long.
  • Ihr I36L72-Compiler bietet Ihnen eine 36-Bit-Version int

OTOH, nehmen wir an, Sie brauchen keine großen Zahlen oder Arrays, aber Sie brauchen Geschwindigkeit. Und intkann auf allen Plattformen groß genug sein, aber es ist nicht unbedingt der schnellste Typ: 64-Bit-Systeme haben normalerweise noch 32-Bit int. Aber Sie können verwenden int_fast16_tdie „schnellste“ Art und erhalten, sei es int, longoder long long.

Es gibt also praktische Anwendungsfälle für die Typen von <stdint.h>. Der Standard - Integer - Typ nicht Mittelwert nichts. Insbesondere longkann dies 32 oder 64 Bit sein und in Abhängigkeit von der Laune der Compiler-Schreiber groß genug sein oder auch nicht, um einen Zeiger aufzunehmen.

dan04
quelle
Ein Problem bei Typen wie uint_least32_tist, dass ihre Wechselwirkungen mit anderen Typen noch schwächer spezifiziert sind als die von uint32_t. IMHO sollte der Standard Typen definieren wie uwrap32_tund unum32_tmit der Semantik, dass jeder Compiler, der einen Typ definiert uwrap32_t, einen vorzeichenlosen Typ in im Wesentlichen den gleichen Fällen heraufstufen muss, wie er bei int32 Bit heraufgestuft würde, und jeder Compiler, der einen Typ definiert, unum32_tmuss dies sicherstellen Grundlegende arithmetische Aktionen wandeln es immer in einen vorzeichenbehafteten Typ um, der seinen Wert halten kann.
Supercat
Darüber hinaus könnte der Standard auch Typen definieren, deren Speicherung und Aliasing mit intN_tund kompatibel uintN_tsind und deren definiertes Verhalten mit und konsistent ist, den Compilern jedoch einige Freiheiten einräumen, wenn Code zugewiesene Werte außerhalb ihres Bereichs liegen [und Semantiken ähnlich denen zulassen, die vorhanden sind Vielleicht gedacht für , aber ohne Unsicherheiten, ob das Hinzufügen von a und an ein vorzeichenbehaftetes oder vorzeichenbehaftetes Ergebnis ergeben würde. intN_tuintN_tuint_least32_tuint_least16_tint32_t
Supercat