Warum gibt das Zuweisen eines Werts zu einem Bitfeld nicht denselben Wert zurück?


96

Ich habe den folgenden Code in diesem Quora-Beitrag gesehen :

#include <stdio.h>

struct mystruct { int enabled:1; };
int main()
{
  struct mystruct s;
  s.enabled = 1;
  if(s.enabled == 1)
    printf("Is enabled\n"); // --> we think this to be printed
  else
    printf("Is disabled !!\n");
}

In C & C ++ ist die Ausgabe des Codes unerwartet .

Ist behindert !!

Obwohl die Erklärung zum "Vorzeichenbit" in diesem Beitrag enthalten ist, kann ich nicht verstehen, wie es möglich ist, dass wir etwas festlegen und es dann nicht so widerspiegelt, wie es ist.

Kann jemand eine ausführlichere Erklärung geben?


Hinweis : Beide Tags & sind erforderlich, da sich ihre Standards zur Beschreibung der Bitfelder geringfügig unterscheiden. Siehe Antworten zur C-Spezifikation und C ++ - Spezifikation .


46
Da das Bitfeld so deklariert ist, wie intich denke, kann es nur die Werte 0und enthalten -1.
Osiris

6
Denken Sie nur daran, wie int -1 speichert. Alle Bits werden auf 1 gesetzt. Wenn Sie also nur ein Bit haben, muss es eindeutig -1 sein. 1 und -1 im 1-Bit-Int sind also gleich. Ändern Sie die Prüfung in 'if (s.enabled! = 0)' und es funktioniert. Weil 0 es nicht sein kann.
Jürgen

3
Es ist wahr, dass diese Regeln in C und C ++ gleich sind. Gemäß den Richtlinien zur Verwendung von Tags sollten wir dies jedoch nur als C kennzeichnen und kein Cross-Tagging durchführen, wenn dies nicht erforderlich ist. Ich werde den C ++ - Teil entfernen, er sollte keine veröffentlichten Antworten beeinflussen.
Lundin

8
Haben Sie versucht, es zu ändern struct mystruct { unsigned int enabled:1; };?
ChatterOne

4
Bitte lesen Sie die C- und C ++ - Tag-Richtlinien , insbesondere den Teil zum Cross-Tagging von C und C ++, der hier durch Community-Konsens festgelegt wurde . Ich gehe nicht in einen Rollback-Krieg, aber diese Frage ist fälschlicherweise mit C ++ gekennzeichnet. Auch wenn die Sprachen aufgrund verschiedener TCs geringfügige Unterschiede aufweisen, stellen Sie eine separate Frage zum Unterschied zwischen C und C ++.
Lundin

Antworten:


77

Bitfelder sind vom Standard unglaublich schlecht definiert. Angesichts dieses Codes wissen struct mystruct {int enabled:1;};wir nicht :

  • Wie viel Platz dies einnimmt - wenn Füllbits / Bytes vorhanden sind und wo sie sich im Speicher befinden.
  • Wo sich das Bit im Speicher befindet. Nicht definiert und hängt auch von der Endianess ab.
  • int:nGibt an, ob ein Bitfeld als signiert oder nicht signiert anzusehen ist.

In Bezug auf den letzten Teil heißt es in C17 6.7.2.1/10:

Ein Bitfeld wird so interpretiert, dass es einen vorzeichenbehafteten oder vorzeichenlosen Ganzzahltyp hat, der aus der angegebenen Anzahl von Bits besteht. 125)

Nicht normativer Hinweis zur Erläuterung des oben Gesagten:

125) Wie in 6.7.2 oben angegeben, ist, wenn der tatsächlich verwendete Typbezeichner intoder ein typedef-Name definiert intist, implementierungsdefiniert, ob das Bitfeld signiert oder nicht signiert ist.

Wenn das Bitfeld als betrachtet werden soll signed intund Sie ein bisschen Größe machen 1, gibt es keinen Platz für Daten, nur für das Vorzeichenbit. Dies ist der Grund, warum Ihr Programm bei einigen Compilern möglicherweise seltsame Ergebnisse liefert.

Gute Übung:

  • Verwenden Sie niemals Bitfelder für irgendeinen Zweck.
  • Vermeiden Sie die Verwendung eines vorzeichenbehafteten intTyps für jede Form der Bitmanipulation.

5
Bei der Arbeit haben wir static_asserts für die Größe und Adresse von Bitfeldern, um sicherzustellen, dass sie nicht aufgefüllt sind. Wir verwenden Bitfelder für Hardwareregister in unserer Firmware.
Michael

4
@Lundin: Das Hässliche an # define-d-Masken und Offsets ist, dass Ihr Code mit Verschiebungen und bitweisen UND / ODER-Operatoren übersät ist. Mit Bitfeldern erledigt der Compiler das für Sie.
Michael

4
@Michael Mit Bitfeldern erledigt der Compiler das für Sie. Nun, das ist in Ordnung, wenn Ihre Standards für "kümmert sich darum" "nicht portabel" und "unvorhersehbar" sind. Meine sind höher als das.
Andrew Henle

3
@AndrewHenle Leushenko sagt, dass es aus der Sicht des C-Standards selbst an der Implementierung liegt, ob er sich für den x86-64-ABI entscheidet oder nicht.
mtraceur

3
@ AndrewHenle Richtig, ich stimme in beiden Punkten zu. Mein Punkt war, dass ich denke, dass Ihre Meinungsverschiedenheit mit Leushenko auf die Tatsache zurückzuführen ist, dass Sie "Implementierung definiert" verwenden, um sich nur auf Dinge zu beziehen, die weder streng durch den C-Standard noch streng durch die Plattform ABI definiert sind, und er verwendet sie, um sich zu beziehen zu allem, was nicht nur durch den C-Standard streng definiert ist.
mtraceur

58

Ich kann nicht verstehen, wie es möglich ist, dass wir etwas einstellen und es dann nicht so angezeigt wird, wie es ist.

Fragen Sie sich, warum es kompiliert oder Ihnen einen Fehler gibt?

Ja, es sollte idealerweise einen Fehler geben. Und das tut es, wenn Sie die Warnungen Ihres Compilers verwenden. In GCC mit -Werror -Wall -pedantic:

main.cpp: In function 'int main()':
main.cpp:7:15: error: overflow in conversion from 'int' to 'signed char:1' 
changes value from '1' to '-1' [-Werror=overflow]
   s.enabled = 1;
           ^

Die Gründe, warum dies der Implementierung überlassen bleibt, im Vergleich zu einem Fehler, haben möglicherweise mehr mit historischen Verwendungen zu tun, bei denen das Erfordernis einer Besetzung bedeuten würde, alten Code zu beschädigen. Die Autoren des Standards glauben möglicherweise, dass Warnungen ausreichten, um die Lücke für die Betroffenen zu schließen.

Um ein wenig Verschreibungspflicht einzubringen, werde ich @ Lundins Aussage wiederholen: "Verwenden Sie niemals Bitfelder für irgendeinen Zweck." Wenn Sie die guten Gründe haben, sich über Ihre Speicherlayoutdetails zu informieren, die Sie zu der Annahme veranlassen würden, dass Sie überhaupt Bitfelder benötigen, werden die anderen damit verbundenen Anforderungen, die Sie mit ziemlicher Sicherheit haben, auf deren Unterspezifikation stoßen.

(TL; DR - Wenn Sie hoch genug sind, um Bitfelder zu "benötigen", sind sie nicht gut genug definiert, um Ihnen zu dienen.)


15
Die Autoren des Standards waren an dem Tag an Feiertagen, an dem das Bitfeldkapitel entworfen wurde. Also musste der Hausmeister es tun. Es gibt keine Gründe dafür , wie Bitfelder entworfen werden.
Lundin

9
Es gibt keine kohärente technische Begründung. Dies führt mich jedoch zu dem Schluss, dass es eine politische Begründung gab: zu vermeiden, dass der vorhandene Code oder die Implementierungen falsch sind. Das Ergebnis ist jedoch, dass Bitfelder nur sehr wenig zu bieten haben.
John Bollinger

6
@ JohnBollinger Es gab definitiv Politik, die C90 großen Schaden zufügte. Ich habe einmal mit einem Mitglied des Komitees gesprochen, das die Ursache für viel Mist erklärte - der ISO-Standard durfte bestimmte vorhandene Technologien nicht bevorzugen. Aus diesem Grund bleiben wir bei schwachsinnigen Dingen wie der Unterstützung des Komplements und der vorzeichenbehafteten Größe von 1, der implementierungsdefinierten Vorzeichen von char, der Unterstützung von Bytes, die nicht 8 Bit usw. sind usw. usw. Sie durften schwachsinnigen Computern keinen Marktnachteil verschaffen.
Lundin

1
@Lundin Es wäre interessant, eine Sammlung von Aufzeichnungen und Obduktionen von Leuten zu sehen, die glaubten, dass Kompromisse irrtümlich gemacht wurden, und warum. Ich frage mich, wie viel Studium dieser "wir haben das letzte Mal gemacht und es hat / hat nicht geklappt" zu institutionellem Wissen geworden ist, um den nächsten solchen Fall zu informieren, im Gegensatz zu nur Geschichten in den Köpfen der Menschen.
HostileFork sagt, vertraue SE

1
Dies ist immer noch als Punkt Nr. Gelistet. 1 der ursprünglichen Prinzipien von C in der C2x-Charta: "Bestehender Code ist wichtig, bestehende Implementierungen nicht." ... "Es wurde keine Implementierung als Beispiel für die Definition von C angeführt: Es wird davon ausgegangen, dass sich alle vorhandenen Implementierungen etwas ändern müssen, um dem Standard zu entsprechen."
Leushenko

23

Dies ist ein implementierungsdefiniertes Verhalten. Ich gehe davon aus, dass die Maschinen, auf denen Sie dies ausführen, vorzeichenbehaftete Ganzzahlen mit zwei Komplimenten verwenden und intin diesem Fall als vorzeichenbehaftete Ganzzahl behandeln, um zu erklären, warum Sie keinen if true-Teil der if-Anweisung eingeben.

struct mystruct { int enabled:1; };

deklariert enableals 1-Bit-Bitfeld. Da es signiert ist, sind die gültigen Werte -1und 0. Setzen Sie das Feld so, 1dass das Bit, auf das zurückgegangen wird, überläuft -1(dies ist ein undefiniertes Verhalten).

Im Wesentlichen ist beim Umgang mit einem vorzeichenbehafteten Bitfeld der Maximalwert 2^(bits - 1) - 1der 0in diesem Fall.


"Sobald es signiert ist, sind die gültigen Werte -1 und 0". Wer hat gesagt, dass es unterschrieben ist? Es ist kein definiertes, sondern implementierungsdefiniertes Verhalten. Wenn es signiert ist, sind die gültigen Werte -und +. Das Komplement von 2 spielt keine Rolle.
Lundin

5
@Lundin Eine 1-Bit-Zweierkomplimentnummer hat nur zwei mögliche Werte. Wenn das Bit gesetzt ist, ist es -1, da es das Vorzeichenbit ist. Wenn es nicht gesetzt ist, ist es "positiv" 0. Ich weiß, dass dies eine definierte Implementierung ist. Ich erkläre nur die Ergebnisse mit der häufigsten Implantation
NathanOliver

1
Der Schlüssel hier ist vielmehr, dass das 2er-Komplement oder eine andere vorzeichenbehaftete Form nicht mit einem einzigen verfügbaren Bit funktionieren kann.
Lundin

1
@ JohnBollinger Ich verstehe das. Deshalb habe ich den Hinweis, dass dies eine Implementierung definiert ist. Zumindest für die großen 3 behandeln sie alle intin diesem Fall als unterschrieben. Es ist eine Schande, dass Bitfelder so unterbestimmt sind. Grundsätzlich ist hier diese Funktion, fragen Sie Ihren Compiler, wie man sie verwendet.
NathanOliver

1
@Lundin, der Wortlaut des Standards für die Darstellung vorzeichenbehafteter Ganzzahlen kann den Fall, in dem mindestens in zwei der drei zulässigen Alternativen Nullwertbits vorhanden sind, perfekt behandeln. Dies funktioniert, weil Zeichenzeichenbits (negative) Platzierungswerte zugewiesen werden, anstatt ihnen eine algorithmische Interpretation zu geben.
John Bollinger

10

Man könnte sich vorstellen, dass im Komplementsystem der 2 das Bit ganz links das Vorzeichenbit ist. Jede vorzeichenbehaftete Ganzzahl mit dem am weitesten links stehenden Bit ist daher ein negativer Wert.

Wenn Sie eine 1-Bit-Ganzzahl mit Vorzeichen haben, hat sie nur das Vorzeichenbit. Die Zuweisung 1zu diesem einzelnen Bit kann also nur das Vorzeichenbit setzen. Beim Zurücklesen wird der Wert als negativ interpretiert, ebenso wie -1.

Die Werte, die eine 1-Bit-Ganzzahl mit Vorzeichen enthalten kann, sind -2^(n-1)= -2^(1-1)= -2^0= -1und2^n-1= 2^1-1=0


8

Gemäß dem C ++ - Standard n4713 wird ein sehr ähnliches Code-Snippet bereitgestellt. Der verwendete Typ ist BOOL(benutzerdefiniert), kann jedoch für jeden Typ gelten.

12.2.4

4 Wenn der Wert true oder false in einem Bitfeldbooleines beliebigenTyps(einschließlich eines Ein-Bit-Bitfelds)gespeichert wird, müssen der ursprünglicheboolWert und der Wert des Bitfelds gleich sein. Wenn der Wert eines Enumerators in einem Bitfeld desselben Aufzählungstyps gespeichert ist und die Anzahl der Bits im Bitfeld groß genug ist, um alle Werte dieses Aufzählungstyps (10.2) aufzunehmen, werden der ursprüngliche Aufzählerwert und der Der Wert des Bitfeldes soll gleich sein . [Beispiel:

enum BOOL { FALSE=0, TRUE=1 };
struct A {
  BOOL b:1;
};
A a;
void f() {
  a.b = TRUE;
  if (a.b == TRUE)    // yields true
    { /* ... */ }
}

- Beispiel beenden]


Auf den ersten Blick erscheint der fette Teil offen für Interpretationen. Die richtige Absicht wird jedoch klar, wenn die enum BOOLvon der abgeleitet wird int.

enum BOOL : int { FALSE=0, TRUE=1 }; // ***this line
struct mystruct { BOOL enabled:1; };
int main()
{
  struct mystruct s;
  s.enabled = TRUE;
  if(s.enabled == TRUE)
    printf("Is enabled\n"); // --> we think this to be printed
  else
    printf("Is disabled !!\n");
}

Mit dem obigen Code wird eine Warnung ausgegeben ohne -Wall -pedantic:

Warnung: 'mystruct :: enabled' ist zu klein, um alle Werte von 'enum BOOL' zu speichern. struct mystruct { BOOL enabled:1; };

Die Ausgabe ist:

Ist behindert !! (bei Verwendung enum BOOL : int)

Wenn dies vereinfacht enum BOOL : intwird, erfolgt enum BOOLdie Ausgabe wie in der obigen Standard-Passage angegeben:

Ist aktiviert (bei Verwendung enum BOOL)


Daher kann, wie auch nur wenige andere Antworten, gefolgert werden, dass der intTyp nicht groß genug ist, um den Wert "1" in nur einem einzelnen Bitbitfeld zu speichern.


0

Es ist nichts Falsches an Ihrem Verständnis von Bitfeldern, das ich sehen kann. Was ich sehe ist, dass Sie Mystruct zuerst als Struct Mystruct neu definiert haben {int enabled: 1; } und dann als struct mystruct s; . Was Sie hätten codieren sollen, war:

#include <stdio.h>

struct mystruct { int enabled:1; };
int main()
{
    mystruct s; <-- Get rid of "struct" type declaration
    s.enabled = 1;
    if(s.enabled == 1)
        printf("Is enabled\n"); // --> we think this to be printed
    else
        printf("Is disabled !!\n");
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.