Christian Seiler: Zahlenformatierung in C

Beitrag lesen

Hallo,

aber nimm zum Beispiel Linux oder Windows auf x86_64 (64 bit) und da ist size_t äquivalent zu einer 64bit-Ganzzahl (unter Linux/x86_64 wäre das also äquivalent zu long, unter Windows/x86_64 äquivalent zu long long) während int auf diesen Systemen weiterhin eine 32bit-Ganzzahl ist.

Dann wären 64bit-Windows oder 64bit-Linux Mogelpackungen - denn die "Bittigkeit" leitet sich ja von der Datenbusbreite ab, und int soll die "natürliche" Datenbreite eines Systems wiedergeben.

Nein. Woher hast Du das? Der C-Standard definiert lediglich, dass int mindestens soundsogroß ist (ich glaube 16 bit, aber ich kann mich auch täuschen), mehr nicht.

Es gibt ja folgende Integer-Datentypen in C: char, short, int, long, long long. Wenn Du nun ein 32bit-System hast, dann wird das typischerwiese so gemappt:

8bit: char
  16bit: short
  32bit: int, long
  64bit: long long (wenn verfügbar)

Folgende Mappings hast Du nun unter x86_64 unter Linux und Windows:

Linux                      Windows
   8bit: char                       char
  16bit: short                      short
  32bit: int                        int, long
  64bit: long, long long            long long

Wenn Du nun vorschlägst, int zu einem 64bit-Datentyp zu machen, was ist dann Dein 32bit-Datentyp?

Wenn ein Compiler für diese Prozessoren int mit nur 32bit definiert, würde man sie de facto wieder auf 32bit-Systeme degradieren

Nein, würde man nicht, es gibt ja auch noch long bzw. long long. Der C-Standard sagt wie gesagt nur etwas über die Mindestgrößen eines Datentyps aus und nicht über die Relation zur Architektur aus.

Im aktuellen C-Standard gibt's zusätzlich den Typen intmax_t (bzw. uintmax_t für unsigned), der garantiert der größte int ist. Auf einem aktuellen x86_64-System wäre das also ein 64bit-Integer, ebenso aber auch auf einem aktuellen x86-System, weil's dort 64bit-Befehle im Prozessor gibt, die einfach jeweils 2 32bit-Register auf einmal nehmen. Wenn man dagegen einen Microprozessor nimmt, dann kann intmax_t aber auch gerne mal 16bit groß sein.

Der Typ size_t ist laut Standard so festgelegt, dass das *immer* für Puffergrößen u.ä. verwendet werden kann, während z.B. ptrdiff_t *immer* für Differenzen zwischen zwei Pointern verwendet werden kann. Genauso gibt's off_t für Positionen in Dateien (was z.B. auch auf einem 32bit-OS eine 64bit-Größe sein kann weil Dateien ja > 4 Gig sein können) und so weiter. Wenn man diese Typen an den korrekten Stellen verwendet, stellt man sicher, dass man den Code überall dort verwenden kann, wo es eine standardkonforme C-Implementierung gibt.

Das heißt aber auch, dass derselbe Code, wenn er auf unterschiedlichen Plattformen compiliert wird, auch unterschiedliche Datenstrukturen erzeugt. Das ist nicht das, was ich unter Portabilität verstehe.

Portabilität heißt im Idealfall (!), dass Du das Programm nur ein einziges Mal schreibst und dann überall ohne Änderungen kompilieren kannst. Wenn nun zwei Plattformen unterschiedliche Datentypen haben (auf 32bit-Systemen sind Pointer 32bit, auf 64bit-Systemen 64bit), dann musst Du das irgendwie ein Einklang bringen. Und da ist so eine Abstraktion eben sinnvoll, weil man dann einfach den korrekten Typen verwenden kann ohne sich darüber Sorgen zu müssen.

Und andere Datenstrukturen hast Du doch eh: Wenn Du auf einem Big-Endian-System kompilierst, dreht sich die Byte-Reihenfolge um im Vergleich zu Intel. Wenn die Größen von int/short/etc. sich ändern, dann hast Du auch andere Datenstrukturen. Ich verstehe da das Problem nicht.

Mal ein Beispiel: Ich hatte vor Jahren, als es noch den einen, alten SELFHTML-Server gab, der in einer Tiefgarage in München stand, ein kleines C-Tool geschrieben, das von den ganzen Artikeln in SELFHTML aktuell die Metatags validiert. Das hatte ich damals auf einem x86 unter Linux entwickelt, mich aber explizit auf ANSI-C beschränkt. Der Server lief damals unter FreeBSD auf einem x86. Als wir dann die neuen Server bekommen haben, habe ich das Ding einfach unter Linux/x86_64 neu kompiliert ohne die geringste Änderung (!) und es lief sofort. Das ist, was ich unter Portabilität verstehe.

Die einzige Stelle, wo es interessant wird, ist der Austausch von Daten. Wenn also ein Programm eine Datei schreibt und die Datei auf einer anderen Architektur wieder eingelesen werden will. Dann werden die tatsächlichen Größen von Datenstrukturen relevant. Aber auch da hat der aktuelle C-Standard vorgesorgt, es gibt int8_t, int16_t, int32_t, int64_t sowie die ganzen unsigned-Varianten (uint8_t, ...), mit dem man eine bestimmte Bit-Größe sicherstellen kann. Letztendlich bietet der aktuelle C-Standard also die nötigen Mittel, um problemlos portable Programme zu schreiben:

* Spezifische Datentypen für bestimmte Zwecke wie Puffergrößen, Datei-
   offsets, etc.
 * Spezifische Datentypen mit festen Bitwerten für den wohldefinierten
   Datenaustausch

Und wie bereits gesagt: Portabilität ist nicht der einzige Vorteil. Wenn man bestimmte Typen wie size_t verwendet, dann weiß man sofort, welche Annahmen in bestimmten Stellen von Code stecken.

Viele Grüße,
Christian

PS: Kleiner Hinweis: intmax_t, int8_t, etc. sind in <stdint.h> definiert im aktuellen C-Standard und keine intrinsischen Typen.