long long int vs. long int vs. int64_t in C ++

85

Ich habe bei der Verwendung von C ++ - Typmerkmalen ein merkwürdiges Verhalten festgestellt und mein Problem auf dieses skurrile kleine Problem eingegrenzt, für das ich eine Menge Erklärungen geben werde, da ich nichts für Fehlinterpretationen offen lassen möchte.

Angenommen, Sie haben ein Programm wie dieses:

#include <iostream>
#include <cstdint>

template <typename T>
bool is_int64() { return false; }

template <>
bool is_int64<int64_t>() { return true; }

int main()
{
 std::cout << "int:\t" << is_int64<int>() << std::endl;
 std::cout << "int64_t:\t" << is_int64<int64_t>() << std::endl;
 std::cout << "long int:\t" << is_int64<long int>() << std::endl;
 std::cout << "long long int:\t" << is_int64<long long int>() << std::endl;

 return 0;
}

Bei der 32-Bit-Kompilierung mit GCC (und mit 32- und 64-Bit-MSVC) lautet die Ausgabe des Programms:

int:           0
int64_t:       1
long int:      0
long long int: 1

Das aus einer 64-Bit-GCC-Kompilierung resultierende Programm gibt jedoch Folgendes aus:

int:           0
int64_t:       1
long int:      1
long long int: 0

Das ist merkwürdig, da long long intist ein signiertes 64-Bit - Integer und ist für alle Absichten und Zwecke, identisch mit dem long intund int64_tTypen, so logisch, int64_t, long intund long long intgleichwertige Typen sein würde - die Anordnung erzeugt wird, wenn diese Arten mit identischem wird. Ein Blick zeigt stdint.hmir warum:

# if __WORDSIZE == 64
typedef long int  int64_t;
# else
__extension__
typedef long long int  int64_t;
# endif

In einer 64-Bit-Kompilierung int64_tist (offensichtlich) long intkein long long int.

Die Lösung für diese Situation ist ziemlich einfach:

#if defined(__GNUC__) && (__WORDSIZE == 64)
template <>
bool is_int64<long long int>() { return true; }
#endif

Aber das ist schrecklich hackisch und lässt sich nicht gut skalieren (tatsächliche Substanzfunktionen uint64_tusw.). Meine Frage ist also: Gibt es eine Möglichkeit, dem Compiler zu sagen, dass a long long intauch a ist int64_t, genau wie es long intist?


Meine ersten Gedanken sind, dass dies aufgrund der Funktionsweise von C / C ++ - Typdefinitionen nicht möglich ist. Es gibt keine Möglichkeit, die Typäquivalenz der Basisdatentypen für den Compiler anzugeben, da dies die Aufgabe des Compilers ist (und dies viele Dinge beschädigen kann) und typedefnur in eine Richtung geht.

Ich bin auch nicht allzu besorgt darüber, hier eine Antwort zu bekommen, da dies ein Super-Duper-Randfall ist, von dem ich nicht vermute, dass sich jemals jemand darum kümmern wird, wenn die Beispiele nicht schrecklich erfunden sind (bedeutet das, dass dies ein Community-Wiki sein sollte?) .


Anhängen : Der Grund, warum ich eine teilweise Vorlagenspezialisierung anstelle eines einfacheren Beispiels verwende, wie:

void go(int64_t) { }

int main()
{
    long long int x = 2;
    go(x);
    return 0;
}

ist, dass das Beispiel noch kompiliert wird, da long long intes implizit in ein konvertierbar ist int64_t.


Anhängen : Die einzige Antwort geht bisher davon aus, dass ich wissen möchte, ob ein Typ 64-Bit ist. Ich wollte die Leute nicht irreführen, zu denken, dass mir das wichtig ist, und hätte wahrscheinlich mehr Beispiele dafür liefern sollen, wo sich dieses Problem manifestiert.

template <typename T>
struct some_type_trait : boost::false_type { };

template <>
struct some_type_trait<int64_t> : boost::true_type { };

In diesem Beispiel some_type_trait<long int>wird a sein , wird es boost::true_typeaber some_type_trait<long long int>nicht sein. Dies ist zwar in C ++ 's Vorstellung von Typen sinnvoll, aber nicht wünschenswert.

Ein anderes Beispiel ist die Verwendung eines Qualifikationsmerkmals wie same_type(das in C ++ 0x-Konzepten häufig verwendet wird):

template <typename T>
void same_type(T, T) { }

void foo()
{
    long int x;
    long long int y;
    same_type(x, y);
}

Dieses Beispiel kann nicht kompiliert werden, da C ++ (korrekt) erkennt, dass die Typen unterschiedlich sind. g ++ kann nicht mit einem Fehler wie dem folgenden kompiliert werden: kein übereinstimmender Funktionsaufruf same_type(long int&, long long int&).

Ich möchte betonen, dass ich verstehe, warum dies geschieht, aber ich suche nach einer Problemumgehung, die mich nicht zwingt, Code überall zu wiederholen.

Travis Gockel
quelle
Gibt Ihr Beispielprogramm aus Neugier für sizeofjeden Typ die gleichen Ergebnisse ? Vielleicht behandelt der Compiler die Größe long long intanders.
Blair Holloway
Haben Sie mit aktiviertem C ++ 0x kompiliert? C ++ 03 hat es nicht <cstdint>, also ist es vielleicht die Tatsache, dass es "dies ist eine Erweiterung" (was es ist) zu sagen hat.
GManNickG
Ja, ich hätte wahrscheinlich angeben sollen, dass ich verwende --std=c++0x. Und ja , sizeof(long long int) == sizeof(long int) == sizeof(int64_t) == 8.
Travis Gockel
1
Niemand hat dies bisher erwähnt, aber für den Fall, dass es übersehen wurde: longund long longsind verschiedene Typen (auch wenn sie die gleiche Größe und Darstellung haben). int64_tist immer ein Alias ​​für einen anderen vorhandenen Typ (trotz seines Namens werden typedefkeine neuen Typen erstellt, sondern nur ein Alias ​​für einen bereits vorhandenen)
MM
3
In den Antworten / Kommentaren fehlt eine wichtige Aussage, die mir geholfen hat, als mich diese Eigenart traf: Verwenden Sie niemals Typen mit fester Größe, um Vorlagen zuverlässig zu spezialisieren. Verwenden Sie immer Basistypen und decken Sie alle möglichen Fälle ab (auch wenn Sie Typen mit fester Größe verwenden, um diese Vorlagen zu instanziieren). Alle möglichen Fälle bedeuten: Wenn Sie mit instanziieren müssen int16_t, dann spezialisieren Sie sich auf shortund intund Sie sind abgesichert. (und mit, signed charwenn Sie sich abenteuerlustig fühlen)
Irfy

Antworten:

47

Sie müssen nicht auf 64-Bit umsteigen, um so etwas zu sehen. Betrachten Sie int32_tauf gängigen 32-Bit-Plattformen. Es kann typedefals intoder als long, aber offensichtlich nur einer der beiden gleichzeitig sein. intund longsind natürlich verschiedene Typen.

Es ist nicht schwer zu erkennen, dass es int == int32_t == longauf 32-Bit-Systemen keine Problemumgehung gibt . Aus dem gleichen Grund gibt es keine Möglichkeit, long == int64_t == long longauf 64-Bit-Systemen zu machen .

Wenn Sie könnten, wären die möglichen Konsequenzen für überlasteten Code ziemlich schmerzhaft foo(int), foo(long)und foo(long long)- plötzlich hätten sie zwei Definitionen für dieselbe Überladung?!

Die richtige Lösung besteht darin, dass sich Ihr Vorlagencode normalerweise nicht auf einen genauen Typ stützen sollte, sondern auf die Eigenschaften dieses Typs. Die gesamte same_typeLogik könnte für bestimmte Fälle noch in Ordnung sein:

long foo(long x);
std::tr1::disable_if(same_type(int64_t, long), int64_t)::type foo(int64_t);

Das heißt, die Überlastung foo(int64_t)ist nicht definiert, wenn sie genau die gleiche ist wie foo(long).

[Bearbeiten] Mit C ++ 11 haben wir jetzt eine Standardmethode, um dies zu schreiben:

long foo(long x);
std::enable_if<!std::is_same<int64_t, long>::value, int64_t>::type foo(int64_t);
MSalters
quelle
1
Traurige Nachrichten sind zB auf 64bit MSVC19 (2017) sizeof() longund intsind identisch, std::is_same<long, int>::valuekehren aber zurück false. Gleiche Verrücktheit unter AppleClang 9.1 unter OSX HighSierra.
Ax3l
2
@ Ax3l: Das ist nicht komisch. Praktisch jeder Compiler seit ISO C 90 hat mindestens ein solches Paar.
MSalters
Das stimmt, es sind verschiedene Typen.
Ax3l
5

Möchten Sie wissen, ob ein Typ mit int64_t identisch ist, oder möchten Sie wissen, ob etwas 64-Bit ist? Aufgrund Ihrer vorgeschlagenen Lösung fragen Sie wahrscheinlich nach letzterer. In diesem Fall würde ich so etwas tun

template<typename T>
bool is_64bits() { return sizeof(T) * CHAR_BIT == 64; } // or >= 64
Logan Capaldo
quelle
1
Vermissen Sie nicht ein returnund ein Semikolon?
Casablanca
1
Trotzdem sollten Sie sizeofdafür verwenden.
Ben Voigt
5
long long int und long int sind nicht vom gleichen Typ, unabhängig davon, ob sie zufällig dieselbe Größe haben oder nicht. Das Verhalten ist nicht fehlerhaft. Das ist nur C ++.
Logan Capaldo
5
Es ist keine Einschränkung der nominalen Eingabe. Es ist eine Einschränkung der bedeutungslosen nominalen Eingabe. Früher war der De-facto-Standard short= 16 Bit, long= 32 Bit und int= native Größe. In diesen Tagen des 64-Bit, intund longnicht Mittel nichts mehr.
Dan04
1
@ dan04: Sie sind nicht mehr oder weniger aussagekräftig als je zuvor. shortist mindestens 16 Bit, intist mindestens 16 Bit und longist mindestens 32 Bit, mit (schlampige Notation folgt) kurz <= int <= lang. Die "alten Zeiten", auf die Sie sich beziehen, existierten nie; Es gab immer Abweichungen innerhalb der durch die Sprache auferlegten Einschränkungen. Der Irrtum "Die ganze Welt ist ein x86" ist genauso gefährlich wie der ältere "Die ganze Welt ist ein VAX-Irrtum"
Keith Thompson
1

Meine Frage lautet also: Gibt es eine Möglichkeit, dem Compiler mitzuteilen, dass ein langer langer int auch ein int64_t ist, genau wie ein langer int?

Dies ist eine gute Frage oder ein gutes Problem, aber ich vermute, die Antwort lautet NEIN.

Auch a long intdarf nicht a sein long long int.


# if __WORDSIZE == 64
typedef long int  int64_t;
# else
__extension__
typedef long long int  int64_t;
# endif

Ich glaube das ist libc. Ich vermute, Sie wollen tiefer gehen.

Bei der 32-Bit-Kompilierung mit GCC (und mit 32- und 64-Bit-MSVC) lautet die Ausgabe des Programms:

int:           0
int64_t:       1
long int:      0
long long int: 1

32-Bit-Linux verwendet das ILP32-Datenmodell. Ganzzahlen, Longs und Zeiger sind 32-Bit. Der 64-Bit-Typ ist a long long.

Microsoft dokumentiert die Bereiche unter Datentypbereiche . Das Sprichwort long longist gleichbedeutend mit __int64.

Das aus einer 64-Bit-GCC-Kompilierung resultierende Programm gibt jedoch Folgendes aus:

int:           0
int64_t:       1
long int:      1
long long int: 0

64-Bit-Linux verwendet das LP64Datenmodell. Longs sind 64-Bit und long long64-Bit. Wie bei 32-Bit dokumentiert Microsoft die Bereiche unter Datentypbereiche und ist noch lange lang __int64.

Es gibt ein ILP64Datenmodell, bei dem alles 64-Bit ist. Sie müssen einige zusätzliche Arbeiten ausführen, um eine Definition für Ihren word32Typ zu erhalten. Siehe auch Artikel wie 64-Bit-Programmiermodelle: Warum LP64?


Aber das ist schrecklich hackisch und lässt sich nicht gut skalieren (tatsächliche Funktionen der Substanz, uint64_t usw.) ...

Ja, es wird noch besser. GCC mischt und stimmt Deklarationen ab, die 64-Bit-Typen annehmen sollen, sodass es leicht zu Problemen kommt, obwohl Sie einem bestimmten Datenmodell folgen. Das Folgende verursacht beispielsweise einen Kompilierungsfehler und fordert Sie auf, Folgendes zu verwenden -fpermissive:

#if __LP64__
typedef unsigned long word64;
#else
typedef unsigned long long word64;
#endif

// intel definition of rdrand64_step (http://software.intel.com/en-us/node/523864)
// extern int _rdrand64_step(unsigned __int64 *random_val);

// Try it:
word64 val;
int res = rdrand64_step(&val);

Es fuehrt zu:

error: invalid conversion from `word64* {aka long unsigned int*}' to `long long unsigned int*'

Ignorieren Sie es LP64und ändern Sie es in:

typedef unsigned long long word64;

Wechseln Sie dann zu einem 64-Bit-ARM-IoT-Gadget, das LP64NEON definiert und verwendet:

error: invalid conversion from `word64* {aka long long unsigned int*}' to `uint64_t*'
jww
quelle