Ist ein "langes" Verbot sinnvoll?

109

In der heutigen plattformübergreifende C ++ (oder C) Welt , die wir haben :

Data model  | short |   int |   long | long long | pointers/size_t  | Sample operating systems
... 
LLP64/IL32P64   16      32      32     64           64                Microsoft Windows (x86-64 and IA-64)
LP64/I32LP64    16      32      64     64           64                Most Unix and Unix-like systems, e.g. Solaris, Linux, BSD, and OS X; z/OS
...

Was dies heute bedeutet, ist, dass für jede "gemeinsame" (vorzeichenbehaftete) Ganzzahl intausreicht und möglicherweise beim Schreiben von C ++ - Anwendungscode immer noch als ganzzahliger Standardtyp verwendet werden kann. Aus praktischen Gründen wird es auch eine einheitliche Größe für alle Plattformen haben.

Wenn ein Anwendungsfall mindestens 64 Bit erfordert, können wir ihn heute verwenden long long, obwohl die Verwendung eines der bitnessspezifizierenden Typen oder des __int64Typs möglicherweise sinnvoller ist.

Dies bleibt longin der Mitte, und wir erwägen, die Verwendung von longCode aus unserem Anwendungscode endgültig zu verbieten .

Wäre dies sinnvoll , oder gibt es einen Grund für die Verwendung longin modernem C ++ - (oder C-) Code, der plattformübergreifend ausgeführt werden muss? (Plattform ist Desktop, mobile Geräte, aber keine Dinge wie Mikrocontroller, DSPs usw.)


Möglicherweise interessante Hintergrundlinks:

Martin Ba
quelle
14
Wie gehen Sie mit langen Anrufen bei Bibliotheken um?
Angel
14
longNur so können 32 Bit garantiert werden. intkann 16 Bit sein, für einige Anwendungen ist dies nicht ausreichend. Ja, intist manchmal 16 Bit auf modernen Compilern. Ja, Menschen schreiben Software auf Mikrocontrollern. Ich würde behaupten, dass mehr Leute Software schreiben, die mehr Benutzer auf Mikrocontrollern als auf PCs hat, mit dem Aufstieg von iPhone- und Android-Geräten, ganz zu schweigen vom Aufstieg von Arduinos usw.
slebetman
53
Warum nicht char, short, int, long und long long verbannen und die [u] intXX_t-Typen verwenden?
immibis
7
@slebetman Ich habe etwas tiefer gegraben, es scheint, dass die Anforderung noch vorhanden ist, obwohl sie in § 3.9.1.3 verborgen ist, in dem der C ++ - Standard lautet: "Die vorzeichenbehafteten und vorzeichenlosen Integer-Typen müssen die in Abschnitt 5.2 des C-Standards angegebenen Einschränkungen erfüllen. 4.2.1. " Und im C-Standard §5.2.4.2.1 wird der Mindestbereich genau so angegeben, wie Sie ihn geschrieben haben. Du hattest absolut recht. :) Anscheinend reicht es nicht aus, eine Kopie des C ++ - Standards zu besitzen. Man muss auch die Kopie des C-Standards finden.
Tommy Andersen
11
Sie vermissen die DOSBox / Turbo C ++ - Welt, in der intes immer noch 16 Bit gibt. Ich hasse es, es zu sagen, aber wenn Sie über die "heutige plattformübergreifende Welt" schreiben, können Sie den gesamten indischen Subkontinent nicht ignorieren.
Leichtigkeitsrennen im Orbit

Antworten:

17

Der einzige Grund, den ich heute verwenden würde, longist das Aufrufen oder Implementieren einer externen Schnittstelle, die diese verwendet.

Wie Sie in Ihrem Posting sagen, weisen short und int heute auf allen wichtigen Desktop- / Server- / Mobilplattformen einigermaßen stabile Eigenschaften auf, und ich sehe keinen Grund, dass sich dies in absehbarer Zukunft ändern wird. Ich sehe daher wenig Grund, sie im Allgemeinen zu meiden.

longauf der anderen Seite ist ein Durcheinander. Mir ist bekannt, dass alle 32-Bit-Systeme die folgenden Eigenschaften aufweisen.

  1. Es war genau 32 Bit groß.
  2. Es hatte die gleiche Größe wie eine Speicheradresse.
  3. Es war so groß wie die größte Dateneinheit, die in einem normalen Register gespeichert werden und mit einem einzigen Befehl bearbeitet werden konnte.

Auf der Grundlage eines oder mehrerer dieser Merkmale wurden große Codemengen geschrieben. Mit der Umstellung auf 64-Bit konnten jedoch nicht alle beibehalten werden. Unix-ähnliche Plattformen entschieden sich für LP64, das die Merkmale 2 und 3 zu Lasten von Merkmal 1 beibehielt. Win64 entschied sich für LLP64, das die Merkmale 1 zu Lasten von Merkmal 2 und 3 behielt. Sie können sich also nicht mehr auf eines dieser Merkmale verlassen und das IMO lässt wenig Grund zu verwenden long.

Wenn Sie einen Typ möchten, der genau 32 Bit groß ist, sollten Sie verwenden int32_t.

Wenn Sie einen Typ möchten, der die gleiche Größe wie ein Zeiger hat, sollten Sie intptr_t(oder besser uintptr_t) verwenden.

Wenn Sie einen Typ wünschen, der das größte Element ist, an dem in einem einzelnen Register / Befehl gearbeitet werden kann, dann glaube ich leider nicht, dass der Standard einen vorsieht. size_tsollte auf den meisten gängigen Plattformen stimmen, aber nicht auf x32 .


PS

Ich würde mich nicht mit den Typen "schnell" oder "am wenigsten" beschäftigen. Die "kleinsten" Typen sind nur dann von Bedeutung, wenn Sie Portabilität bevorzugen, um Architekturen wirklich zu verschleiern CHAR_BIT != 8. Die Größe der "schnellen" Typen in der Praxis scheint ziemlich willkürlich zu sein. Linux scheint sie mindestens so groß wie Pointer zu machen, was auf 64-Bit-Plattformen mit schneller 32-Bit-Unterstützung wie x86-64 und arm64 albern ist. IIRC iOS macht sie so klein wie möglich. Ich bin nicht sicher, was andere Systeme tun.


PPS

Ein Grund für die Verwendung unsigned long(aber nicht einfach long) ist, dass ein Modulo-Verhalten garantiert ist. Leider haben aufgrund der verkorksten Promotion-Regeln von C unsignierte Typen, die kleiner intsind, kein Modulo-Verhalten.

Heutzutage ist auf allen wichtigen Plattformen uint32_tdie gleiche Größe oder größer als int und weist daher ein Modulo-Verhalten auf. Es gab jedoch in der Vergangenheit und es könnte theoretisch in Zukunft Plattformen geben, auf denen int64-Bit verfügbar ist und daher uint32_tkein Modulo-Verhalten vorliegt.

Persönlich würde ich sagen, dass es besser ist, das Modulo-Verhalten durch die Verwendung von "1u *" oder "0u +" am Anfang Ihrer Gleichungen zu erzwingen, da dies für jede Größe von vorzeichenlosem Typ funktioniert.

Peter Green
quelle
1
Alle Typen mit der angegebenen Größe wären viel nützlicher, wenn sie eine Semantik angeben könnten, die sich von den integrierten Typen unterscheidet. Zum Beispiel wäre es nützlich, einen Typ zu haben, der eine Mod-65536-Arithmetik unabhängig von der Größe von "int" verwendet, zusammen mit einem Typ, der die Zahlen 0 bis 65535 halten kann, aber willkürlich und nicht notwendigerweise konsistent fähig sein könnte von Zahlen größer als das Halten. Welcher Größentyp am schnellsten ist, hängt auf den meisten Computern vom jeweiligen Kontext ab. Wenn der Compiler also willkürlich auswählen kann, ist dies für die Geschwindigkeit optimal.
Supercat
204

Wie Sie in Ihrer Frage erwähnen, dreht sich bei moderner Software alles um die Interaktion zwischen Plattformen und Systemen im Internet. Die C- und C ++ - Standards geben Bereiche für ganzzahlige Schriftgrößen an, nicht für bestimmte Größen (im Gegensatz zu Sprachen wie Java und C #).

Um sicherzustellen, dass Ihre auf verschiedenen Plattformen kompilierte Software auf dieselbe Weise mit denselben Daten arbeitet und dass andere Software mit Ihrer Software in derselben Größe interagieren kann, sollten Sie Ganzzahlen mit fester Größe verwenden.

Geben Sie ein, <cstdint>welches genau das bereitstellt und ein Standardheader ist, den alle Compiler- und Standardbibliotheksplattformen bereitstellen müssen. Hinweis: Dieser Header wurde erst ab C ++ 11 benötigt, wurde jedoch von vielen älteren Bibliotheksimplementierungen trotzdem bereitgestellt.

Willst du eine 64-Bit-Ganzzahl ohne Vorzeichen? Verwenden Sie uint64_t. Vorzeichenbehaftete 32-Bit-Ganzzahl? Verwenden Sie int32_t. Während die Typen im Header optional sind, sollten moderne Plattformen alle in diesem Header definierten Typen unterstützen.

Manchmal wird eine bestimmte Bitbreite benötigt, beispielsweise in einer Datenstruktur, die für die Kommunikation mit anderen Systemen verwendet wird. Ein anderes Mal ist es nicht. Stellt für weniger strenge Situationen <cstdint>Typen mit einer Mindestbreite bereit.

Es gibt die wenigsten Varianten: int_leastXX_tWird ein ganzzahliger Typ mit mindestens XX Bits sein. Es wird der kleinste Typ verwendet, der XX-Bits bereitstellt, der Typ darf jedoch größer als die angegebene Anzahl von Bits sein. In der Praxis sind dies typischerweise dieselben wie die oben beschriebenen Typen, die die genaue Anzahl von Bits angeben.

Es gibt auch schnelle Varianten: int_fastXX_tmindestens XX Bits, sollte aber einen Typ verwenden, der auf einer bestimmten Plattform schnell arbeitet. Die Definition von "schnell" in diesem Zusammenhang ist nicht spezifiziert. In der Praxis bedeutet dies jedoch typischerweise, dass ein Typ, der kleiner als die Registergröße einer CPU ist, ein Alias ​​für einen Typ der Registergröße der CPU sein kann. Beispielsweise gibt der Header von Visual C ++ 2015 an, dass int_fast16_tes sich um eine 32-Bit-Ganzzahl handelt, da die 32-Bit-Arithmetik auf x86 insgesamt schneller ist als die 16-Bit-Arithmetik.

Dies ist alles wichtig, da Sie Typen verwenden sollten, die die Ergebnisse von Berechnungen enthalten können, die Ihr Programm unabhängig von der Plattform ausführt. Wenn ein Programm auf einer Plattform aufgrund von Unterschieden beim Ganzzahlüberlauf korrekte Ergebnisse, auf einer anderen jedoch falsche Ergebnisse liefert, ist dies schlecht. Wenn Sie die Standard-Integer-Typen verwenden, stellen Sie sicher, dass die Ergebnisse auf verschiedenen Plattformen hinsichtlich der Größe der verwendeten Ganzzahlen gleich sind (natürlich kann es neben der Integer-Breite auch andere Unterschiede zwischen den Plattformen geben).

Also ja, longsollte aus modernem C ++ Code verbannt werden. So soll int, shortund long long.


quelle
20
Ich wünschte, ich hätte noch fünf andere Konten, um dies noch etwas zu verbessern.
Steven Burnap
4
+1, ich habe einige seltsame Speicherfehler behoben, die nur auftreten, wenn die Größe einer Struktur von dem Computer abhängt, auf dem Sie kompilieren.
Joshua Snider
9
@Wildcard ist ein C-Header, der auch Teil von C ++ ist: siehe das Präfix "c" darauf. Es gibt auch eine Möglichkeit, die typedefs in den stdNamespace einzufügen, wenn #included in einer C ++ - Kompilierungseinheit verwendet wird. In der Dokumentation, die ich verlinkt habe, wird dies jedoch nicht erwähnt, und es scheint Visual Studio egal zu sein, wie ich darauf zugreife.
11
Das Verbot intkann ... übertrieben sein? (Ich würde es in Betracht ziehen, wenn der Code extrem portabel auf allen undurchsichtigen (und nicht so undurchsichtigen) Plattformen sein muss. Ein Verbot für "App-Code" passt möglicherweise nicht sehr gut zu unseren Entwicklern.
Martin Ba
5
@Snowman #include <cstdint>ist erforderlich , die Arten in setzen std::und (leider) optional erlaubt auch sie in dem globalen Namensraum zu setzen. #include <stdint.h>ist genau das Gegenteil. Gleiches gilt für jedes andere Paar von C-Headern. Siehe: stackoverflow.com/a/13643019/2757035. Ich wünschte, der Standard hätte verlangt, dass sich jeder nur auf den jeweils erforderlichen Namespace auswirkt, anstatt sich an die schlechten Konventionen zu knicken , die von einigen Implementierungen festgelegt wurden.
Underscore_d
38

Nein, es wäre absurd, die eingebauten Integer-Typen zu verbieten. Sie sollten jedoch auch nicht missbraucht werden.

Wenn Sie eine Ganzzahl benötigen, die genau N Bits breit ist, verwenden Sie (oder wenn Sie eine Version benötigen ). Es ist einfach falsch, sich eine 32-Bit-Ganzzahl und eine 64-Bit-Ganzzahl vorzustellen. Auf Ihren aktuellen Plattformen ist dies möglicherweise der Fall, dies setzt jedoch implementierungsspezifisches Verhalten voraus.std::intN_tstd::uintN_tunsignedintlong long

Die Verwendung von Integer-Typen mit fester Breite ist auch für die Interaktion mit anderen Technologien nützlich. Wenn beispielsweise einige Teile Ihrer Anwendung in Java und andere in C ++ geschrieben sind, möchten Sie wahrscheinlich die Ganzzahltypen abgleichen, um konsistente Ergebnisse zu erzielen. (Beachten Sie jedoch, dass der Überlauf in Java eine genau definierte Semantik aufweist, während der signedÜberlauf in C ++ undefiniertes Verhalten ist, sodass die Konsistenz ein hohes Ziel darstellt.) Sie sind auch beim Datenaustausch zwischen verschiedenen Computerhosts von unschätzbarem Wert.

Wenn Sie nicht genau N Bits benötigen , sondern nur einen Typ, der breit genug ist , sollten Sie die Verwendung von (platzoptimiert) oder (geschwindigkeitsoptimiert) in Betracht ziehen . Auch hier haben beide Familien Kollegen.std::int_leastN_tstd::int_fastN_tunsigned

Wann sollten die eingebauten Typen verwendet werden? Nun, da der Standard ihre Breite nicht genau spezifiziert, verwenden Sie sie, wenn Sie sich nicht um die tatsächliche Bitbreite, sondern um andere Eigenschaften kümmern.

A charist die kleinste Ganzzahl, die von der Hardware adressiert werden kann. Die Sprache zwingt Sie tatsächlich, sie zum Aliasing von beliebigem Speicher zu verwenden. Es ist auch der einzige realisierbare Typ für die Darstellung von (schmalen) Zeichenfolgen.

Ein intTyp ist normalerweise der schnellste Typ, den die Maschine verarbeiten kann. Es ist breit genug, damit es mit einem einzigen Befehl geladen und gespeichert werden kann (ohne dass Bits maskiert oder verschoben werden müssen) und schmal genug, damit es mit (den effizientesten) Hardwareanweisungen bearbeitet werden kann. Daher intist es die perfekte Wahl für die Übergabe von Daten und das Rechnen, wenn ein Überlauf kein Problem darstellt. Der zugrunde liegende Standardtyp für Aufzählungen ist beispielsweise int. Ändern Sie es nicht in eine 32-Bit-Ganzzahl, nur weil Sie können. Wenn Sie einen Wert haben, der nur -1, 0 und 1 sein kann, ist einintist eine perfekte Wahl, es sei denn, Sie speichern riesige Arrays von ihnen. In diesem Fall möchten Sie möglicherweise einen kompakteren Datentyp verwenden, wenn Sie für den Zugriff auf einzelne Elemente einen höheren Preis zahlen müssen. Eine effizientere Zwischenspeicherung wird sich wahrscheinlich auszahlen. Viele Betriebssystemfunktionen sind auch in Bezug auf definiert int. Es wäre dumm, ihre Argumente und Ergebnisse hin und her zu konvertieren. All dies möglicherweise tun könnten , ist einführen Überlauffehler.

longwird normalerweise der breiteste Typ sein, der mit Einzelmaschinenanweisungen gehandhabt werden kann. Dies ist besonders unsigned longattraktiv für den Umgang mit Rohdaten und allem, was mit Bitmanipulation zu tun hat. Zum Beispiel würde ich erwarten, unsigned longin der Implementierung eines Bitvektors zu sehen . Wenn der Code sorgfältig geschrieben wird, spielt es keine Rolle, wie breit der Typ tatsächlich ist (da sich der Code automatisch anpasst). Auf Plattformen, auf denen das native Maschinenwort 32-Bit ist, ist das Backing-Array des Bitvektors ein Array vonunsigned32-Bit-Ganzzahlen sind am wünschenswertesten, da es dumm wäre, einen 64-Bit-Typ zu verwenden, der über teure Anweisungen geladen werden muss, um die nicht benötigten Bits ohnehin wieder zu verschieben und zu maskieren. Wenn die native Wortgröße der Plattform hingegen 64 Bit beträgt, möchte ich ein Array dieses Typs, da Vorgänge wie "find first set" möglicherweise doppelt so schnell ausgeführt werden. Das „Problem“ des longDatentyps, den Sie beschreiben, dass seine Größe von Plattform zu Plattform variiert, ist also ein Feature , das man gut nutzen kann. Es wird nur dann zum Problem, wenn Sie sich die eingebauten Typen als Typen mit einer bestimmten Bitbreite vorstellen, die sie einfach nicht sind.

char, intUnd longsind sehr nützlich Typen wie oben beschrieben. shortund long longsind bei weitem nicht so nützlich, weil ihre Semantik viel weniger klar ist.

5gon12eder
quelle
4
Das OP wies insbesondere auf den Größenunterschied longzwischen Windows und Unix hin. Ich mag missverstehen, aber Ihre Beschreibung des Unterschieds in der Größe long, ein "Feature" statt eines "Problems" zu sein, macht für mich Sinn, wenn ich 32- und 64-Bit-Datenmodelle vergleiche, aber nicht für diesen speziellen Vergleich. In dem speziellen Fall, in dem diese Frage gestellt wurde, ist dies wirklich ein Merkmal? Oder ist es ein Merkmal in anderen Situationen (das heißt im Allgemeinen) und in diesem Fall harmlos?
Dan Getz
3
@ 5gon12eder: Das Problem ist, dass Typen wie uint32_t erstellt wurden, um zu ermöglichen, dass das Verhalten von Code unabhängig von der Größe von "int" ist, aber das Fehlen eines Typs, dessen Bedeutung "Verhalten wie ein uint32_t" lauten würde, funktioniert auf einem 32- "bit system" erschwert das Schreiben von Code, dessen Verhalten unabhängig von der Größe von "int" korrekt ist, erheblich als das Schreiben von Code, der fast korrekt ist.
Supercat
3
Ja, ich weiß ... das war der Grund, warum der Fluch kam. Die ursprünglichen Autoren haben gerade den Weg des Mietwiderstands eingeschlagen, da 32-Bit-Betriebssysteme mehr als ein Jahrzehnt entfernt waren, als sie den Code geschrieben haben.
Steven Burnap
8
@ 5gon12eder Leider ist supercat richtig. Alle die sind genau Breitentypen „typedefs nur“ , und die nehmen Förderung Regeln ganze Zahl von ihnen keine Notiz, die Arithmetik auf bedeutet , dass , uint32_twie durchgeführt werden Werte unterzeichnet , intauf einer Plattform -width Arithmetik , wo intist breiter als uint32_t. (Mit heutigen ABIs ist dies mit überwältigender Wahrscheinlichkeit ein Problem für uint16_t.)
zwol
9
1. danke für eine ausführliche antwort. Aber: Oh mein Lieber. Ihr langer Absatz: " longWird normalerweise der breiteste Typ sein, der mit Einzelmaschinenanweisungen verarbeitet werden kann. ..." - und das ist genau falsch . Schauen Sie sich das Windows-Datenmodell an. IMHO, dein ganzes folgendes Beispiel bricht zusammen, weil auf x64 Windows noch 32 bit lang ist.
Martin Ba
6

In einer anderen Antwort wird bereits auf die CSTDINT-Typen und weniger bekannte Variationen eingegangen.

Ich möchte noch hinzufügen:

Verwenden Sie domänenspezifische Typnamen

Das heißt, nicht erklären , Ihre Parameter und Variablen zu sein uint32_t(schon gar nicht long!), Aber Namen wie channel_id_type, room_count_typeusw.

über Bibliotheken

Bibliotheken von Drittanbietern, die longoder was auch immer verwenden, können ärgerlich sein, insbesondere wenn sie als Verweise oder Zeiger auf diese verwendet werden.

Das Beste ist, Wrapper zu machen.

Meine Strategie besteht im Allgemeinen darin, eine Reihe von cast-ähnlichen Funktionen zu erstellen, die verwendet werden. Sie sind überladen, um nur die Typen zu akzeptieren, die genau den entsprechenden Typen entsprechen, zusammen mit den von Ihnen benötigten Zeigervariationen usw. Sie werden spezifisch für das Betriebssystem / den Compiler / die Einstellungen definiert. Auf diese Weise können Sie Warnungen entfernen und dennoch sicherstellen, dass nur die "richtigen" Konvertierungen verwendet werden.

channel_id_type cid_out;
...
SomeLibFoo (same_thing_really<int*>(&cid_out));

Insbesondere bei verschiedenen primitiven Typen, die 32 Bit erzeugen, stimmt Ihre Auswahl der int32_tDefinition möglicherweise nicht mit dem Bibliotheksaufruf überein (z. B. int vs long unter Windows).

Die cast-ähnliche Funktion dokumentiert den Konflikt, ermöglicht die Überprüfung des mit den Funktionsparametern übereinstimmenden Ergebnisses während der Kompilierung und beseitigt Warnungen oder Fehler nur dann, wenn der tatsächliche Typ mit der tatsächlichen Größe übereinstimmt. Das heißt, es ist überladen und definiert, wenn ich (unter Windows) ein int*oder a übergebe long*und andernfalls einen Fehler bei der Kompilierung gebe.

Wenn also die Bibliothek aktualisiert wird oder jemand ändert, was channel_id_typeist, wird dies weiterhin überprüft.

JDługosz
quelle
Warum die Gegenstimme (ohne Kommentar)?
JDługosz
Weil die meisten Downvotes in diesem Netzwerk ohne Kommentare erscheinen ...
Ruslan