19 Stimmen

Reihenfolge der Felder beim Verwenden eines Bit-Felds in C

Ich habe eine Struktur vom folgenden Typ

typedef struct
{
unsigned int a : 8;
unsigned int b : 6;
unsigned int c : 2;
}x, *ptr;

Was ich tun möchte, ist den Wert des Feldes c zu ändern.

Ich mache etwas Ähnliches wie das Folgende

x Struktur = { 0 };
x->c = 1;

Wenn ich mir die Speicherkarte anschaue, erwarte ich 00 01 zu finden, aber stattdessen finde ich 00 40. Es scheint, dass beim Anordnen des zweiten Bytes das c-Feld in die niedrigsten Bits und das b-Feld in die höchsten Bits gestellt wird. Ich habe dies bei beiden GCC- und Windows-Compilern gesehen.

Bis jetzt mache ich das Folgende, was gut funktioniert.

unsigned char ptr2 = (unsigned char*) ptr
*(ptr2 + 1)  &= 0xFC
*(ptr2 + 1)  |= 0x01

Schaue ich mir die Speicherkarte falsch an? Vielen Dank für deine Hilfe.

26voto

user694733 Punkte 14478

Der C-Standard erlaubt es dem Compiler, Bitfelder in beliebiger Reihenfolge zu platzieren. Es gibt keinen zuverlässigen und portablen Weg, die Reihenfolge zu bestimmen.

Wenn Sie die genauen Bit-Positionen wissen müssen, ist es besser, einfache unsigned Variablen und Bitmasken zu verwenden.

Hier ist eine mögliche Alternative zur Verwendung von Bitfeldern:

#include 

#define MASK_A    0x00FF
#define MASK_B    0x3F00
#define MASK_C    0xC000
#define SHIFT_A   0
#define SHIFT_B   8
#define SHIFT_C   14

unsigned GetField(unsigned all, unsigned mask, unsigned shift)
{
    return (all & mask) >> shift;
}

unsigned SetField(unsigned all, unsigned mask, unsigned shift, unsigned value)
{
    return (all & ~mask) | ((value << shift) & mask);
}

unsigned GetA(unsigned all)
{
    return GetField(all, MASK_A, SHIFT_A);
}

unsigned SetA(unsigned all, unsigned value)
{
    return SetField(all, MASK_A, SHIFT_A, value);
}

/* Ähnliche Funktionen für B und C hier */

int main(void)
{
    unsigned myABC = 0;
    myABC = SetA(myABC, 3);
    printf("%u", GetA(myABC)); // Gibt 3 aus
}

15voto

jrelles Punkte 81

Ich weiß, das ist alt, aber ich möchte meine Gedanken hinzufügen.

  1. Header in C sind dazu gedacht, über Objekte hinweg verwendbar zu sein, was bedeutet, dass der Compiler etwas konsistent sein muss.

  2. In meiner Erfahrung habe ich Bitfelder immer in LSB-Reihenfolge gesehen. Dies setzt die Bits in MSB->LSB-Reihenfolge von c:b:a

Die 16 Bits, in Byte-Reihenfolge gelesen, sind "00 40". Übersetzt aus Little-Endian ergibt dies einen 16-Bit-Wert von 0x4000.

Dies ist:

c == [15:14] == b'01
b == [13:8] == 0
a == [7:0] == 0

7voto

scienceplease Punkte 131

Sie können sich darauf verlassen, dass die Reihenfolge der Bitfelder deterministisch ist, solange Ihr Programm absichtlich nicht plattformübergreifend ist.

Obwohl die C-Spezifikation die Reihenfolge von Bitfeldern nicht vorschreibt, tut es das ABI der Plattform. Im Fall von System V AMD 64 ist die Zuweisungsreihenfolge von Bitfeldern als "rechts nach links" definiert (Abschnitt 3.1.2). Für ARM 64 hängt es von der Endianness des Datentyps ab (Abschnitt 8.1.8.2). Da sich Ihr C-Compiler an das Plattform-ABI der Zielsystemarchitektur hält, können Sie sich auf eine feste Zuweisungsreihenfolge von Bitfeldern innerhalb einer bestimmten Plattform verlassen.

2voto

Dwayne Robinson Punkte 1552

Wenn ich mir die Speicherabbildung anschaue, erwarte ich, dass ich 00 01 finde, stattdessen finde ich aber 00 40. Es sieht so aus, als würde beim Anordnen des zweiten Bytes das Feld c in die niedrigsten Bits und das Feld b in die höchsten Bits setzen. Ich habe das sowohl bei GCC als auch bei Windows-Compilern gesehen.

Ignorieren wir für einen Moment die Endianness, wenn Sie alle Felder in einem rein sequentiellen Strom von zunehmenden Bits anordnen, erhalten Sie die unten aufgeführte Zuweisung in der Spalte "Bitstromreihenfolge", und Sie sehen, dass das letzte Feld in Ihrer Struktur c das letzte Feld im Strom ist. Nun, wenn Sie die Dinge in Bytegruppen aufteilen, erhalten Sie vier Permutationen, einschließlich der Reihenfolge des kleinen Endian (die tatsächlich identisch ist mit der reinen Bitstromreihenfolge) und zwei Big-Endian-Reihenfolgen.

Compiler, die Windows anvisieren, bauen nur für Little-Endian-Maschinen (früher musste der Kernel mit einigen Big-Endian-Maschinen umgehen, aber alle aktuellen kompatiblen Architekturen sind Little-Endian - x86/x64/arm64), und das Schreiben von 1 in Feld c (Bit c0 unten) würde 1<<6 oder 0b01000000 (0x40) im Byte 1 ergeben.

Auf Big-Endian-Maschinen gibt es mehr als eine mögliche Anordnung:

  1. die Bitfelder in absteigender Reihenfolge vom signifikantesten Bit im Wort zum unbedeutendsten zu platzieren und jedes Byte innerhalb dieses Wortes auszutauschen (mit hohen Bytes an niedrigen Adressen und niedrigen Bytes an hohen Adressen). Diese Anordnung finden Sie in gcc unter Linux mit Big-Endian-Maschinen.
  2. die Bitfelder in aufsteigender Reihenfolge vom niederwertigsten zum höchstwertigen Bit zu platzieren, wie LE, und ganze Bytes innerhalb des Wortes auszutauschen. Diese finden Sie im C51 8051 Mikrocontroller-Compiler.

Absolutes Bit

Bitstromreihenfolge

Byte

Bit im Byte

LE-Reihenfolge

BE-Reihenfolge#1

BE-Reihenfolge#2

0

a0

0

0

a0

a0

b0

1

a1

0

1

a1

a1

b1

2

a2

0

2

a2

a2

b2

3

a3

0

3

a3

a3

b3

4

a4

0

4

a4

a4

b4

5

a5

0

5

a5

a5

b5

6

a6

0

6

a6

a6

c0

7

a7

0

7

a7

a7

c1

8

b0

1

0

b0

c0

a0

9

b1

1

1

b1

c1

a1

10

b2

1

2

b2

b0

a2

11

b3

1

3

b3

b1

a3

12

b4

1

4

b4

b2

a4

13

b5

1

5

b5

b3

a5

14

c0

1

6

c0

b4

a6

15

c1

1

7

c1

b5

a7

0voto

Peter Miehle Punkte 5908

Der Speicher hängt immer von der zugrunde liegenden Maschinenstruktur (Endianität) und von der Strategie ab, mit der der Compiler die Struktur packt/anordnet.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X