Datentypen mit fester Länge in C / C ++

74

Ich habe gehört, dass die Größe der Datentypen intje nach Plattform variieren kann.

Meine erste Frage ist: Kann jemand ein Beispiel bringen, was schief geht, wenn das Programm int4 Bytes annimmt , auf einer anderen Plattform jedoch 2 Bytes?

Eine andere Frage, die ich hatte, ist verwandt. Ich kenne Leute , dieses Problem mit einigen lösen typedefs, wie Sie Variablen wie haben u8, u16, u32- die 8bits, 16bit, 32bit sein, unabhängig von der Plattform garantiert werden - meine Frage ist, wie dies in der Regel erreicht wird? (Ich beziehe mich nicht auf Typen aus der stdintBibliothek - ich bin manuell neugierig, wie kann man erzwingen, dass ein Typ unabhängig von der Plattform immer 32 Bit sagt?)


quelle
3
Es gibt potenzielle Probleme beim Überschreiben des Speichers. Wenn Sie davon ausgehen, dass eine Ganzzahl 4 Byte beträgt, wenn sie auf einer anderen Plattform 2 Byte beträgt, können Sie die nächsten 2 Byte nach Ihrer Ganzzahl überschreiben, je nachdem, wie der Speicher aufgebaut ist.
Austin Brunkhorst
9
Ein guter Zeitpunkt, um die (alten, aber immer noch äußerst informativen) C-FAQ aus Usenet-Tagen zu lesen: faqs.org/faqs/C-faq/abridged und dann faqs.org/faqs/C-faq/faq (ungekürzt, also wenn Sie kann das stattdessen lesen! viele weitere Infos). Es geht um viele davon und auch um viele andere oft falsche Annahmen (interne Darstellung von NULL usw.). (Ein Muss ist das Kapitel über Null und über Zeiger / Arrays. Der Rest ist auch gut und öffnet die Augen für viele Themen)
Olivier Dulac
1
Bitte beachten Sie, dass die Bytereihenfolge auch von Plattform zu Plattform variieren kann. (+1 für Frage - es ist besser, eine Frage zu stellen, als anzunehmen, "sicherlich sizeof(void *)wird das immer so sein 4).
Maciej Piechotka
@ MaciejPiechotka: vereinbart. und es ist gut, diese zu posten, da viele Leser sich dann der möglichen Gefahr und ihrer Lösungen bewusst werden könnten! Es gibt keine schlechten Fragen [naja, wenn sie genug Kontext geben], nur schlechte Antworten ^^
Olivier Dulac

Antworten:

41

Ich weiß, dass Leute dieses Problem mit einigen Typedefs lösen, wie Sie Variablen wie u8, u16, u32 haben - die garantiert 8 Bit, 16 Bit, 32 Bit sind, unabhängig von der Plattform

Es gibt einige Plattformen, die keine Typen bestimmter Größe haben (wie zum Beispiel 28xxx von TI, wo die Größe von char 16 Bit beträgt). In solchen Fällen ist es nicht möglich, einen 8-Bit-Typ zu haben (es sei denn, Sie möchten es wirklich, aber dies kann zu Leistungseinbußen führen).

Wie wird dies normalerweise erreicht?

Normalerweise mit typedefs. c99 (und c ++ 11) haben diese Typedefs in einem Header . Also benutze sie einfach.

Kann jemand ein Beispiel bringen, was schief geht, wenn das Programm davon ausgeht, dass ein Int 4 Bytes beträgt, auf einer anderen Plattform jedoch 2 Bytes?

Das beste Beispiel ist eine Kommunikation zwischen Systemen mit unterschiedlicher Typgröße. Wenn Sie eine Reihe von Ints von einer zu einer anderen Plattform senden, bei der sizeof (int) bei zwei unterschiedlich ist, muss Sie äußerst vorsichtig sein.

Speichern Sie außerdem eine Reihe von Ints in einer Binärdatei auf einer 32-Bit-Plattform und interpretieren Sie sie auf einer 64-Bit-Plattform neu.

BЈовић
quelle
14
+1 zum Speichern eines Ints-Arrays in einer Binärdatei auf einer 32-Bit-Plattform und zum erneuten Interpretieren auf einer 64-Bit-Plattform. .
Legends2k
22

In früheren Iterationen des C-Standards haben Sie im Allgemeinen Ihre eigenen typedefAnweisungen gemacht, um sicherzustellen, dass Sie einen (zum Beispiel) 16-Bit-Typ erhalten, der auf #defineZeichenfolgen basiert, die beispielsweise an den Compiler übergeben wurden:

gcc -DINT16_IS_LONG ...

Heutzutage (C99 und höher) gibt es bestimmte Typen wie uint16_tdie genau 16 Bit breite Ganzzahl ohne Vorzeichen.

Vorausgesetzt, Sie geben an stdint.h, erhalten Sie genaue Bitbreitentypen, Typen mit mindestens dieser Breite, schnellste Typen mit einer bestimmten Mindestbreite usw., wie in dokumentiert C99 7.18 Integer types <stdint.h>. Wenn eine Implementierung kompatible Typen hat, müssen diese bereitgestellt werden.

Sehr nützlich ist auch inttypes.hdas Hinzufügen einiger weiterer nützlicher Funktionen für die Formatkonvertierung dieser neuen Typen ( printfund scanfFormatzeichenfolgen).

paxdiablo
quelle
1
Unterfrage: Wenn die Plattform keinen 16-Bit-Integer-Typ unterstützt, ist unint16_tin cstdintetc .. nicht definiert ? Oder garantiert der Standard, dass der Typ immer vorhanden ist (und intern Dinge erledigt, um sicherzustellen, dass er funktioniert)?
Martin York
5
Nein, der C-Standard erfordert dies nur , wenn die Implementierung einen kompatiblen Typ hat. Wenn Sie auf einem 12-Bit - DSP zum Beispiel ausgeführt wird , dann ist es nicht hat einen 16-Bit uint16_t bereitzustellen. Es kann, aber es ist nicht obligatorisch:7.18.1.1/3: These types are optional. However, if an implementation provides integer types with widths of 8, 16, 32, or 64 bits, no padding bits, and (for the signed types) that have a two’s complement representation, it shall define the corresponding typedef names.
paxdiablo
4
Wenn Sie also verwenden uint16_tund die Plattform dies nicht unterstützt, können wir während der Portierungsprozesse einen Kompilierungsfehler erwarten.
Martin York
1
@Loki, ja, der Compiler kennt den Typ nicht.
Paxdiablo
16

Zur ersten Frage: Integer Overflow .

Für die zweite Frage: Verwenden Sie beispielsweise für typedefeine vorzeichenlose 32-Bit-Ganzzahl auf einer Plattform mit int4 Byte Folgendes:

 typedef unsigned int u32;

Auf einer Plattform mit int2 Bytes und long4 Bytes:

typedef unsigned long u32;

Auf diese Weise müssen Sie nur eine Header-Datei ändern, um die Typen plattformübergreifend zu gestalten.

Wenn es einige plattformspezifische Makros gibt, kann dies ohne manuelle Änderung erreicht werden:

#if defined(PLAT1)
typedef unsigned int u32;
#elif defined(PLAT2)
typedef unsigned long u32;
#endif

Wenn C99 stdint.hunterstützt wird, wird es bevorzugt.

Yu Hao
quelle
Egal, es gibt solche Zeiten ... - mach eine Pause!
Alk
Was ist hier eine Plattform? Ist es die Hardware - wie x86, x86_64, AMD usw. ... oder ist es ein Betriebssystem - wie Solaris, AIX, HP-UX, Linux, MacOS, BSD und IBM z / OS usw.?
Darshan L
8

Zunächst einmal: Nie Schreibprogramme , die auf die Breite der Typen verlassen möchten short, int, unsigned int, ....

Grundsätzlich gilt: "Verlassen Sie sich niemals auf die Breite, wenn dies nicht durch den Standard garantiert wird".

Wenn Sie wirklich plattformunabhängig sein und beispielsweise den Wert 33000 als vorzeichenbehaftete Ganzzahl speichern möchten, können Sie nicht einfach davon ausgehen, dass ein intdiesen Wert enthält . An inthat mindestens den Bereich -32767bis 32767oder -32768bis 32767(abhängig von Einsen / Zweien-Komplement). Das ist einfach nicht genug, obwohl es normalerweise 32 Bit ist und daher 33000 speichern kann. Für diesen Wert benötigen Sie definitiv einen >16bitTyp, daher wählen Sie einfach int32_toder int64_t. Wenn dieser Typ nicht vorhanden ist, teilt Ihnen der Compiler den Fehler mit, es handelt sich jedoch nicht um einen stillen Fehler.

Zweitens: C ++ 11 bietet einen Standardheader für Ganzzahltypen mit fester Breite. Keines davon ist garantiert auf Ihrer Plattform vorhanden, aber wenn es vorhanden ist, hat es garantiert die exakte Breite. In diesem Artikel auf cppreference.com finden Sie eine Referenz. Die Typen werden in dem Format genannt int[n]_tund uint[n]_two nist 8, 16, 32oder 64. Sie müssen den Header einfügen <cstdint>. Der CHeader ist natürlich <stdint.h>.

stefan
quelle
2
OP: " Ich beziehe mich nicht auf Typen aus der stdint-Bibliothek - ich bin manuell neugierig, wie kann man erzwingen, dass ein Typ unabhängig von der Plattform immer 32 Bit sagt?) ";
Legends2k
2
@ legends2k Der richtige Weg, um Ganzzahltypen mit fester Breite zu haben, ist die Verwendung von Standardbibliotheken.
Stefan
4
Einverstanden, aber das ist, wenn Sie Code schreiben, nicht wenn Sie versuchen zu lernen, wie solche Header überhaupt geschrieben werden.
Legends2k
7
" Zuallererst: Schreiben Sie niemals Programme, die von der Breite der Typen abhängen. " Sie sagen also, wir sollten uns nicht darauf verlassen, uint32_tdass sie 32 Bit breit sind? Abstraktionen sind nett und alles, aber irgendwann kommt ein Punkt, an dem Sie einige Annahmen treffen müssen, um tatsächlich etwas zu erledigen.
Thomas
6
Was meinst du mit "Schreibe niemals Programme, die von der Breite der Typen abhängen"? Die Breite der Typen wirkt sich direkt auf den Bereich möglicher Werte aus. Dies ist sehr wichtig, wenn Sie auswählen, welche Typen verwendet werden sollen, insbesondere für die Art von Programmieraufgaben, für die viele Benutzer C / C ++ verwenden. Wenn Sie ein Dateisystem schreiben oder etwas, das viele Werte im eingeschränkten Speicher speichern muss, müssen Sie diese Art von Entscheidungen treffen. Es gibt einen Grund, warum Zeichenfolgen nicht als Arrays mit langer Länge ohne Vorzeichen gespeichert werden.
Tfinniga
6

Normalerweise tritt das Problem auf, wenn Sie die Anzahl maximal nutzen oder wenn Sie serialisieren. Ein weniger häufiges Szenario tritt auf, wenn jemand eine explizite Größenannahme macht.

Im ersten Szenario:

int x = 32000;
int y = 32000;
int z = x+y;        // can cause overflow for 2 bytes, but not 4

Im zweiten Szenario

struct header {
int magic;
int w;
int h;
};

dann geht man zum fwrite:

header h;
// fill in h
fwrite(&h, sizeof(h), 1, fp);

// this is all fine and good until one freads from an architecture with a different int size

Im dritten Szenario:

int* x = new int[100];
char* buff = (char*)x;


// now try to change the 3rd element of x via buff assuming int size of 2
*((int*)(buff+2*2)) = 100;

// (of course, it's easy to fix this with sizeof(int))

Wenn Sie einen relativ neuen Compiler verwenden, würde ich uint8_t, int8_t usw. verwenden, um die Typgröße zu gewährleisten.

In älteren Compilern wird typedef normalerweise pro Plattform definiert. Zum Beispiel kann man tun:

 #ifdef _WIN32
      typedef unsigned char uint8_t;
      typedef unsigned short uint16_t;
      // and so on...
 #endif

Auf diese Weise würde es einen Header pro Plattform geben, der die Besonderheiten dieser Plattform definiert.

thang
quelle
2
+1 für die ersten, die Strukturen erwähnen. Sie sollten auch wissen, was passiert, wenn Sie einen Strct über das Netzwerk senden.
James Anderson
5

Ich bin manuell neugierig, wie kann man durchsetzen, dass ein Typ unabhängig von der Plattform immer 32 Bit sagt?

Wenn die Kompilierung Ihres (modernen) C ++ - Programms fehlschlagen soll, wenn ein bestimmter Typ nicht die erwartete Breite hat, fügen Sie static_assertirgendwo einen hinzu. Ich würde dies hinzufügen, wenn die Annahmen über die Breite des Typs getroffen werden.

static_assert(sizeof(int) == 4, "Expected int to be four chars wide but it was not.");

chars Auf den am häufigsten verwendeten Plattformen sind 8 Bit groß, aber nicht alle Plattformen funktionieren auf diese Weise.

Chwarr
quelle
3
sizeofGibt tatsächlich die Größe in chars zurück, nicht in Bytes. Wenn Sie also die Größe in Bit überprüfen möchten , sollten Sie dies tun sizeof(int) * CHAR_BIT == 32.
user694733
static_assert ist nur im neuesten Standard verfügbar. Aber uint_32t und ähnliche Typen sind von früher verfügbar
Sam
@ user694733 Nr. Größe in Zeichen = Größe in Bytes, per Definition. sizeof(char)==1- immer.
Konrad Rudolph
@sammy Nope uint32_tusw. wurden gleichzeitig mit hinzugefügt static_assert.
Konrad Rudolph
@KonradRudolph Es hängt von der Definition des Bytes ab. Byte wird normalerweise als 8 Bit betrachtet. charhat immer CHAR_BITBits. CHAR_BITist mindestens 8, kann aber mehr sein.
user694733
3

Nun, erstes Beispiel - so etwas:

int a = 45000; // both a and b 
int b = 40000; // does not fit in 2 bytes.
int c = a + b; // overflows on 16bits, but not on 32bits

Wenn Sie in schauen cstdintHeader, werden Sie feststellen , wie alle feste Größe Typen ( int8_t, uint8_tusw.) definiert sind - und einzige , was unterscheidet zwischen verschiedenen Architekturen sind diese Header - Datei. Eine Architektur int16_tkönnte also sein:

 typedef int int16_t;

und auf einem anderen:

 typedef short int16_t;

Es gibt auch andere Typen, die nützlich sein können, wie: int_least16_t

Nemanja Boric
quelle
2
  1. Wenn ein Typ kleiner ist als Sie denken, kann er möglicherweise keinen Wert speichern, den Sie darin speichern müssen.
  2. Um Typen mit fester Größe zu erstellen, lesen Sie die Dokumentation zu den zu unterstützenden Plattformen und definieren dann typedefs basierend auf #ifdefden jeweiligen Plattformen.
Juraj Blaho
quelle
2

Kann jemand ein Beispiel bringen, was schief geht, wenn das Programm davon ausgeht, dass ein Int 4 Bytes beträgt, auf einer anderen Plattform jedoch 2 Bytes?

Angenommen, Sie haben Ihr Programm so konzipiert, dass es 100.000 Eingaben liest, und Sie zählen es mit einer unsigned intangenommenen Größe von 32 Bit (32-Bit-Ints ohne Vorzeichen können bis zu 4.294.967.295 zählen). Wenn Sie den Code auf einer Plattform (oder einem Compiler) mit 16-Bit-Ganzzahlen kompilieren (16-Bit-Ints ohne Vorzeichen können nur bis 65.535 zählen), wird der Wert aufgrund der Kapazität über 65535 hinausgehen und eine falsche Anzahl anzeigen.

legends2k
quelle
1

Compiler sind dafür verantwortlich, den Standard einzuhalten. Wenn Sie einschließen <cstdint>oder <stdint.h>sie Typen gemäß Standardgröße bereitstellen.

Compiler wissen, dass sie den Code für welche Plattform kompilieren, und können dann einige interne Makros oder Magics generieren, um den geeigneten Typ zu erstellen. Ein Compiler auf einem 32-Bit-Computer generiert beispielsweise ein __32BIT__Makro und hat zuvor folgende Zeilen in der stdintHeader-Datei:

#ifdef __32BIT__
typedef __int32_internal__ int32_t;
typedef __int64_internal__ int64_t;
...
#endif

und du kannst es benutzen.

masoud
quelle
0

Bitflags sind das triviale Beispiel. 0x10000 verursacht Probleme, Sie können nicht damit maskieren oder prüfen, ob ein Bit an dieser 17. Position gesetzt ist, wenn alles abgeschnitten oder zerschlagen wird, um in 16-Bit zu passen.

Jheriko
quelle