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. int
und long
) durch Typen mit fester Größe wie uint64_t
und 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 int
die 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 int
in der Ära von C99 und darüber hinaus?
quelle
Antworten:
Es gibt einen weit verbreiteten und gefährlichen Mythos, der es
uint32_t
Programmierern erspart, sich Gedanken über die Größe von zu machenint
. Während es hilfreich wäre, wenn das Normungskomitee ein Mittel zur Deklaration von Ganzzahlen mit maschinenunabhängiger Semantik definieren würde, haben vorzeichenlose Typenuint32_t
eine Semantik, die zu locker ist, um Code sauber und portabel schreiben zu können. Ferner haben signierte Typen wieint32
Semantiken, die für viele Anwendungen unnötig genau definiert sind und somit verhindern, dass ansonsten nützliche Optimierungen vorgenommen werden.Betrachten Sie zum Beispiel:
Auf Computern, auf denen
int
4294967295 oder 18446744065119617025 nicht gespeichert werden können, wird die erste Funktion für alle Werte vonn
und definiertexponent
, und ihr Verhalten wird nicht von der Größe von beeinflusstint
. Darüber hinaus verlangt der Standard nicht, dass sich das Verhalten auf Computern mit einer Größe vonint
einigen Werten von unterscheidet,n
undexponent
veranlasst ihn jedoch, Undefiniertes Verhalten auf Computern aufzurufen, auf denen 4294967295 darstellbar istint
, 18446744065119617025 jedoch nicht.Die zweite Funktion liefert ein undefiniertes Verhalten für einige Werte von
n
undexponent
auf Maschinen, auf denenint
4611686014132420609 nicht gespeichert werden kann, liefert jedoch ein definiertes Verhalten für alle Werte vonn
undexponent
auf allen Maschinen, auf denen dies möglich ist (die Spezifikationen fürint32_t
implizieren, dass zwei das Umhüllungsverhalten auf Maschinen ergänzen, auf denen es vorhanden ist) ist kleiner alsint
).Historisch gesehen, obwohl der Standard nichts darüber sagte, was Compiler mit
int
Überlauf tun solltenupow
, hätten Compiler durchweg das gleiche Verhalten ergeben, als obint
sie 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.quelle
pow
, der den Code manuell implementieren möchte , sollte sich daran erinnern, dass dieser Code nur ein Beispiel ist und keine Lösung bietetexponent=0
!exponent=1
n 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.N^(2^exponent)
. Berechnungen der FormN^(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.uint32_t
mit 31 wird niemals UB ergeben, aber das effiziente Weg, um 31 ^ N zu berechnen, beinhaltet Berechnungen von 31 ^ (2 ^ N), die werdenint32_t
Manchmal 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.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 habensize_t
,ptrdiff_t
,intptr_t
usw. 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
, undlong long
sind immer noch nützlich.IME ist es immer noch üblich, dass C- und C ++ - Programme
int
fü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 verwendenlong
, 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
long
64 Bit, wodurch sich die Speichernutzung verdoppelt. Und wenn dieslong
zufällig Elemente eines Arrays mit Millionen von Elementen sind, wird die Erinnerung wie durchgeknallt.Also, weder
int
nochlong
ist eine geeignete Art hier zu verwenden , wenn Sie Ihr Programm soll sowohl plattformübergreifende und speichereffizient sein. Eintretenint_least32_t
.long
, wodurch die Kürzungsprobleme von vermieden werdenint
int
, wodurch die Verschwendung von 64-Bit-Speicher vermieden wirdlong
.int
OTOH, nehmen wir an, Sie brauchen keine großen Zahlen oder Arrays, aber Sie brauchen Geschwindigkeit. Und
int
kann auf allen Plattformen groß genug sein, aber es ist nicht unbedingt der schnellste Typ: 64-Bit-Systeme haben normalerweise noch 32-Bitint
. Aber Sie können verwendenint_fast16_t
die „schnellste“ Art und erhalten, sei esint
,long
oderlong long
.Es gibt also praktische Anwendungsfälle für die Typen von
<stdint.h>
. Der Standard - Integer - Typ nicht Mittelwert nichts. Insbesonderelong
kann 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.quelle
uint_least32_t
ist, dass ihre Wechselwirkungen mit anderen Typen noch schwächer spezifiziert sind als die vonuint32_t
. IMHO sollte der Standard Typen definieren wieuwrap32_t
undunum32_t
mit der Semantik, dass jeder Compiler, der einen Typ definiertuwrap32_t
, einen vorzeichenlosen Typ in im Wesentlichen den gleichen Fällen heraufstufen muss, wie er beiint
32 Bit heraufgestuft würde, und jeder Compiler, der einen Typ definiert,unum32_t
muss dies sicherstellen Grundlegende arithmetische Aktionen wandeln es immer in einen vorzeichenbehafteten Typ um, der seinen Wert halten kann.intN_t
und kompatibeluintN_t
sind 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_t
uintN_t
uint_least32_t
uint_least16_t
int32_t