Standard C2X

ANSI/ISO-C-Standard C99 — Padding bits

Konsequenzen bei strikt konformer C-Programmierung

© Helmut Schellong, August 2006, Juni 2021


Padding
bits

Der  C-Standard (1999) sagt aus, daß in allen Integer-Typen mit Ausnahme der  char-Typen
Padding-Bits vorkommen können, also neuartige Bits neben den Wert-Bits und dem
Sign-Bit in den vorzeichenbehafteten Integern.

Im Zusammenhang können Trap-Repräsentationen der Bits entstehen. (Trap: Signal, Exception, Interrupt)

Der Standard definiert nicht Anzahl, Positionen und Bedeutungen dieser Padding-Bits.
Ebenso ist nicht definiert, daß diese Bits in allen Typen gleich sein sollen, falls sie existieren.

Hardware
mit
Padding
bits

Ich habe mich seit geraumer Zeit dafür interessiert, mal ein Bit-Layout
mit darin enthaltenen Padding-Bits zu sehen.

Zwei der gefundenen Dokumente (s.u.) habe ich näher betrachtet:
  • Beides Supercomputer-Bereich; Defense-Analyse.
  • Stichworte: Horizon/HEP, TERA/MTA
  1. Maschinenwort: 64 Bit, Vorzeichen, 2er-Komplement.
    Damit assoziiert:
    • indirect-bit
    • full/empty-bit
    • trap-bits 0..3
    Adresse: 48 Bit, Adressenwert 42 Bit MSB
    Pointer: 64 Bit, darin enthalten die 6 Sonderbits.
    Instruktions-Ströme werden synchronisiert.
  2. Mit jedem Memory-Wort assoziiert:
    • forwarding-bit
    • full/empty-bit
    • trap-bits 0..1
Nichts ist davon zu sehen, daß diese Spezial-Bits gemeinsam gespeichert werden
in irgendwelchen Daten-Integern.
Ausnahmslos sind solche Komponenten in Pointern gespeichert.
Pointer sind aber ohnehin schon (immer) speziell, besonders wenn man z.B. NULL und
FAR-Pointer berücksichtigt.

Konse-
quenzen

  • Wenn Daten von einem Programm binär herausgegeben werden, sind sie wegen der
    enthaltenen spezifischen Padding-Bits nahezu vollkommen unportabel - und umgekehrt.
    Man denke auch an Hex-dump-Leseprogramme.
  • Operator sizeof kann nicht mehr verwendet werden, um den Zahlenbereich festzustellen.
    Besonders glücklich ist eine solche Verwendung allerdings ohnehin nicht.
  • sizeof liefert den Speicherbedarf, wobei jetzt unbekannt ist, wie groß der jeweilige Anteil
    von Wert-Bits (+Sign-Bit), Füll-Bits und Padding-Bits ist.
  • Gleiche Werte (==) bedeuten nicht mehr eine gleiche Objektrepräsentation.
  • Ein Objekt eines Typs muß nicht mehr (irgend)einen Wert dieses Typs enthalten, sondern kann
    insgesamt eine Trap-Repräsentation sein.
  • Jegliches Pointer-Casting ist nicht mehr möglich (außer mit void*).
    Es ist zu beachten, daß char, signed char, unsigned char drei unterschiedliche Typen sind.
  • Damit zusammenhängend sind unions nicht mehr möglich, die dazu dienen sollen,
    mit verschieden breiten Typen (Effizienz!) beliebig zuzugreifen:
    union { struct einzelbits128 bit;
    .              unsigned char b[16];
    .              unsigned  int w[8];
    .              unsigned long l[4];
    .     } bit_bwl;
    Man darf nur noch mit demjenigen Typ zugreifen, mit dem direkt zuvor geschrieben wurde.
  • memset() kann nur noch unsigned char Objekte korrekt setzen.
  • memcmp() kann nur noch unsigned char Objekte korrekt wertmäßig vergleichen.
    Sind hier zwei Strukturen ungleich, können dennoch alle Werte darin gleich sein.
  • memcpy() erstellt blind eine bit-getreue Kopie, inklusive der Padding-Bits.
    Aber möglicherweise ist das falsch!, weil beispielsweise beim Kopieren ohne memcpy
    Full/Empty-Bits von 0 auf 1 gesetzt werden - als Unterschied.
    Immerhin hat die Kopie andere Adressen -> Inkonsistenz!

  • Die Unsicherheiten im Umgang mit Bitfeldern werden noch größer.

Gedanken
und
Meinung

Prozessoren transportieren Daten stets in Maschinenwortbreite.

Pointer haben auf einer Plattform nur eine Speicherbreite: Maschinenwortbreite.
Integer-Typen gibt es jedoch in mehreren Breiten:
  • Maschinenwort: 64 Bit  (beispielhaft)
  • Integer 8 Bit
  • Integer 32 Bit
  • Integer 64 Bit
  • Integer 128 Bit
Annahme: Konzeptionell werden 4 Padding-Bits gebraucht.
Sind diese oben enthalten oder werden sie hinzugefügt?!

Problematisch ist sicherlich: 4 Padding + 1 Sign + 3 Wert-Bits = 8 Bits!
Ebenfalls problematisch: 4 Padding + 60 Wert-Bits = 64 Bits
Desweiteren problematisch: 64 Wert-Bits + 4 Padding + 60 Füllbits = 128 Bits!

Weiterhin problematisch:
In einem Maschinenwort können z.B. 8 Bytes gespeichert werden.
Jedes mit eigenem Padding-Feld?
Oder ein Padding-Feld für das Maschinenwort? Dann passen nur noch 7 Wert-Bytes hinein.
Aber wenn die Bytes keine eigenen Padding-Bits mehr haben, dann ...

In einem Pointer jedoch können vollkommen unproblematisch z.B. 48 Adressen-Bits
und 6 Padding-Bits und 10 Füll-Bits gespeichert werden.

Eine Akzeptanz für Wertbereiche, die nicht auf einer Anzahl Wert-Bits  2N (-1)
beruhen, wird es wohl nicht geben.

Von der Bedeutung eines Padding-Bits her ist nur ein Paritäts-Bit oder eine breitere
Prüfsumme prinzipiell sinnvoll, in einem integer Objekt gespeichert zu sein.
Praktisch jedoch nicht:
Ein Paritäts-Bit war bereits in Gemeinschaft mit einem 7Bit-Wert reichlich unsicher,
und breitere Prüfsummen reduzieren den Wertbereich stark.

Ein ReadOnly-Bit wäre unsinnig.
Es müßte ja jedesmal ein Objekt gelesen werden, um diesen Bit-Wert zu erfahren.
Einen Pointer hingegen braucht man für jeden Zugriff ohnehin.

Auch Zugriffssperren sind über Adressen sinnvoll realisiert/realisierbar.

Schon vor langer Zeit hatten manche Prozessoren getrennte Daten- und Adressen-Register.
Auch dies deutet darauf hin, daß Pointer strukturiert werden können/sollten.

Innerhalb von Daten-Integern jedoch haben meiner Meinung nach
nur Wert-Bits oder Sign+Wert-Bits eine sinnvolle Daseinsberechtigung.
Padding-Bits darin wären vollkommener technischer Unsinn!
  • Wert-Bits und Sign-Bit sind wertbildend im Objekt.
  • Prüfsummen-Bits sind direkt wertabhängig.
  • Andere Bits sind wertunabhängig:
    • Bedeutung eines Objekts; Status
    • Trap-Bits
    • Zugriff erlauben/sperren
    • Bestimmte Operationen freigeben
    • Zugehörigkeit angeben
Der absteigende Sinngrad der Bits innerhalb von Daten-Integern ist oben deutlich erkennbar.

Ich glaube nicht, daß ein solcher Prozessor jemals am Markt erscheinen wird.
Praxisgerecht ist es wohl nicht, hier pauschal strikt konform zu programmieren.
Es sei denn, man 'verliert' gar nichts dadurch oder es ist unbedingt gefordert.

Dies ist eine pragmatische Sichtweise:
Welche Vorteile brächte es in der Praxis?
Welche Nachteile brächte es in der Praxis?
Allgemein erwartet man von einem neuen Prozessor nur Vorteile und keine Nachteile!

Natürlich kann ein Prozessor mit Padding-Bits innerhalb der Daten-Integer geschaffen werden.
Dieser könnte/würde sogar irgendwelche Vorteile aufgrund dieser Padding-Bits besitzen.
Ich kann nicht behaupten, daß hier niemals Vorteile resultieren können!
Aber, ohne (triftige) Nachteile kann solches nicht einhergehen!
Es ist beispielsweise der Sicherheitsaspekt zu beachten:
Allein eine vielfältige exotische Abweichung eines solchen Prozessors gegenüber den
bisherigen Prozessoren erhöhte die Wahrscheinlichkeit für beliebige Fehler.

Der C-Standard hat durch seine Definition der möglichen Padding-Bits bereits die
Fehlerwahrscheinlichkeit stark erhöht! - Ja, das ist so!
Es ist etwas Neues aufgetaucht, das mannigfaltig beachtet werden muß/soll
und das konkrete zusätzliche Aktionen verursacht und vieles schwieriger macht.
Der C-Standard hat den Programmierern in ihre Integer gespuckt!
.

Mehr
Details

Padding-Bits sind übersetzt Füll-Bits.
Dennoch meint der Standard hier eher Bits wie Parity-Bits und weitere Bits, die von ihrer Bedeutung
her Trap-Repräsentationen ergeben können.
Der Wert von Füll-Bits ist nämlich irrelevant und wird mit X gekennzeichnet.

Füll-Bits sind auch schon länger weit verbreitet und haben bisher nicht gestört.
Beispielsweise werden 80 Bit breite Gleitkommawerte auf 32Bit-Plattformen
mittels 3×32 = 96 Bit übertragen, es werden also 16 Füll-Bits angehängt.
Und Füll-Bytes gibt es schon immer innerhalb von Strukturen zwecks Alignment.

Es wird oft gefragt, was denn der Sinn der Padding-Bits sein könnte.
Geantwortet werden meist falsche Erklärungen:
  • Exotische Formate beschränken sich auf den Embedded-Bereich.
    Mikrokontroller: Das ist korrekt, sie sind aber auch dort nicht verbreitet,
    was die Basis-Integer-Typen angeht.
  • Es werden zwei Integer in einem Objekt gespeichert und da könne es
    ein 0-Bit dazwischen geben zwecks Effizienz.
    Nein, es handelt sich um ein auf 0 gesetztes Sign-Bit in Pack-Formaten.
  • Es werden Integer innerhalb von Gleitkomma-Formaten verwendet, mit auf 0
    gesetzten Exponenten-Bits.
    Das kann sein, aber der Typ lautet dann möglicherweise __xint, um auf einem
    32Bit-Kontroller besonders lange Integer zu verarbeiten.
    (Die FPU z.B. der iX86 hat 4 Integer-Formate, darunter gepackte BCD 18-stellig.)
Der C-Standard nennt als Beispiel für ein Padding-Bit ein Parity-Bit.
In der Praxis jedoch werden bei Übertragungen anspruchsvolle Prüfverfahren auf ganze
Datenblöcke angewandt, oder der Arbeitsspeicher wird per ECC geschützt, und zwar
vollkommen transparent, etc.
Es ist nicht ersichtlich, was Prüfsummen innerhalb eines jeden einzelnen
Dateninteger-Objektes so sinnvoll macht.

Infineon TriCore 32Bit — C99-Compiler Tasking/Altium

Alle Typen, von _Bool über long long bis long double, sind vorhanden.
Alle Integer: 0 .. 2N-1 bzw. -2N-1 .. 2N-1-1, 2er-Komplement.
N:  8, 16, 32, 64.    Also Nn = 2 Nn-1.
Nur Wert-Bits und Sign-Bit vorhanden.

Typen der Spezial-Formate:
(signed / unsigned)
__frac   __sfrac   __laccum   __packb   __packuw


Es findet also eine Abgrenzung statt, mittels plattform-spezifischer Schlüsselwörter.

Aus dem C-Standard:

  • Byte: It is possible to express the address of each individual byte of an object uniquely.
    A byte is composed of a contiguous sequence of bits, the number of which is implementation-defined.  The least significant bit is called the low-order bit; the most significantt bit is called the high-order bit.
  • CHAR_BIT  8
    number of bits for smallest object that is not a bit-field (byte)
  • SCHAR_MAX  +127
  • The value UCHAR_MAX shall equal 2CHAR_BIT-1
  • Values stored in unsigned bit-fields and objects of type unsigned char shall be represented using a pure binary notation.
  • Values stored in non-bit-field objects of any other object type consist of n × CHAR_BIT bits, where n is the size of an object of that type, in bytes.
  • A byte contains CHAR_BIT bits, and the values of type unsigned char range from 0 to 2CHAR_BIT-1.
  • The sizeof operator yields the size (in bytes) of its operand.
    When applied to an operand that has type char, unsigned char, or signed char, the result is 1.
Annahme:  Es werden 6 Padding-Bits konzeptionell benötigt:

Typ
Bytes
Size-Bits
Wert-Bits (+Sign)
Füll-Bits
char
1
16
8 .. 10
0 .. 2
signed char
1
16
8 .. 10
0 .. 2
unsigned char
1
16
16
0
{CHAR_BIT}
-
{16}
-
-
(unsigned) short
2
32
16 .. 26
0 .. 10
() int
3
48
32 .. 42
0 .. 10
() long
3
48
32 .. 42
0 .. 10
() long long
5
80
64 .. 74
0 .. 10

Es ist ein ganz mieses Konzept, wenn

  • die Speicherbreite sich nicht aufsteigend verdoppelt.
  • die Wertbreite sich nicht aufsteigend verdoppelt.
  • Füll-Bits existieren, da diese bis auf's Füllen unnütz sind
    und dabei auch noch Ressourcen verschwenden.
Fazit: Man kann es drehen und wenden wie man will - mit Padding-Bits kommt stets
ein Typen-Modell heraus, das Mist ist.

Aufgrund von Definitionen des C-Standards läuft es immer auf ein doppelt so breites
Objekt hinaus, sobald Padding-Bits für ein bestehendes Objekt überlegt werden!
Zur Zeit erscheint nur die Möglichkeit, Padding-Bits in einem 128bit-Format von vornherein
vorzusehen, weil hier der Zahlenbereich immens ist.

Links