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

Lesezeit: 11 Minuten

Benutzeravatar von iammilind
iammilind

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

#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");
}

Sowohl in C als auch in C++ ist die Ausgabe des Codes unerwartet,

Ist behindert !!

Obwohl die Erklärung zum “Vorzeichenbit” in diesem Beitrag gegeben wird, kann ich nicht verstehen, wie es möglich ist, dass wir etwas einstellen und es dann nicht so widerspiegelt, wie es ist.

Kann jemand eine genauere Erklärung geben?


NotizHinweis: Beide Tags c & c++ sind erforderlich, da sich ihre Standards zur Beschreibung der Bitfelder leicht unterscheiden. Siehe Antworten für C-Spezifikation und C++-Spezifikation.

  • Da das Bitfeld als deklariert ist int Ich denke, es kann nur die Werte halten 0 und -1.

    – Osiris

    19. Dezember 2018 um 14:45 Uhr

  • Denken Sie nur daran, wie int -1 speichert. Alle Bits sind auf 1 gesetzt. Wenn Sie also nur ein Bit haben, muss es eindeutig -1 sein. Also sind 1 und -1 im 1-Bit-Int gleich. Ändern Sie die Überprüfung auf “if (s.enabled != 0)” und es funktioniert. Denn 0 kann es nicht sein.

    – Jürgen

    19. Dezember 2018 um 15:09 Uhr

  • Es stimmt, dass diese Regeln in C und C++ gleich sind. Aber gemäß den Richtlinien zur Verwendung von Tags sollten wir dies nur als C taggen und auf Cross-Tagging verzichten, wenn es nicht erforderlich ist. Ich werde den C++-Teil entfernen, er sollte keine geposteten Antworten beeinflussen.

    – Ludin

    19. Dezember 2018 um 16:06 Uhr

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

    – ChatterOne

    19. Dezember 2018 um 16:08 Uhr

  • 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 TC geringfügige Unterschiede aufweisen, stellen Sie eine separate Frage zum Unterschied zwischen C und C++.

    – Ludin

    20. Dezember 2018 um 8:04 Uhr

Benutzeravatar von Lundin
Lundin

Bitfelder sind durch den Standard unglaublich schlecht definiert. Angesichts dieses Codes struct mystruct {int enabled:1;};dann wir nicht kennt:

  • Wie viel Speicherplatz dies belegt – ob 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 Endianess ab.
  • Ob ein int:n bitfield ist als signiert oder unsigniert zu betrachten.

Zum letzten Teil sagt C17 6.7.2.1/10:

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

Nicht normativer Hinweis zur Erläuterung des Obigen:

125) Wie in 6.7.2 oben angegeben, wenn der tatsächliche Typbezeichner verwendet wird int oder ein Typedef-Name definiert als intdann ist es implementierungsdefiniert, ob das Bitfeld vorzeichenbehaftet oder vorzeichenlos ist.

Falls das Bitfeld als zu betrachten ist signed int und Sie machen ein bisschen Größe 1, dann gibt es keinen Platz für Daten, nur für das Vorzeichenbit. Dies ist der Grund, warum Ihr Programm bei manchen Compilern seltsame Ergebnisse liefern kann.

Gute Übung:

  • Verwenden Sie niemals Bitfelder für irgendeinen Zweck.
  • Vermeiden Sie die Verwendung von signiert int Typ für jede Form der Bitmanipulation.

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

    – Michael

    19. Dezember 2018 um 19:38 Uhr

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

    – Michael

    20. Dezember 2018 um 8:25 Uhr


  • @ Michael Bei Bitfeldern erledigt das der Compiler für Sie. Nun, das ist in Ordnung, wenn Ihre Standards für “kümmert sich darum” “nicht portierbar” und “unvorhersehbar” sind. Meine sind höher.

    – Andreas Henle

    20. Dezember 2018 um 10:56 Uhr


  • @AndrewHenle Leushenko sagt das aus der Perspektive von nur der C-Standard selbstliegt es an der Implementierung, ob sie sich dafür entscheidet, der x86-64-ABI zu folgen oder nicht.

    – mtraceur

    20. Dezember 2018 um 22:41 Uhr

  • @AndrewHenle Richtig, ich stimme in beiden Punkten zu. Mein Punkt war, dass ich denke, dass Ihre Meinungsverschiedenheit mit Leushenko darauf hinausläuft, 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 es, um sich darauf zu beziehen auf alles, was nicht streng durch den C-Standard definiert ist.

    – mtraceur

    20. Dezember 2018 um 23:20 Uhr


HostileFork sagt, traue dem Benutzer-Avatar von SE nicht
HostileFork sagt, vertraue SE nicht

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, warum es kompiliert und Ihnen einen Fehler gibt?

Ja, es sollte Ihnen 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 Begründung, warum dies Implementierungsdefiniert oder Fehler ist, hat möglicherweise mehr mit historischen Verwendungen zu tun, bei denen das Erfordernis einer Umwandlung das Brechen von altem Code bedeuten würde. Die Autoren des Standards mögen glauben, dass Warnungen ausreichten, um die Lücke für die Betroffenen zu schließen.

Um etwas Präskriptivismus einzubringen, werde ich die Aussage von @Lundin wiederholen: “Verwenden Sie niemals Bitfelder für irgendeinen Zweck.” Wenn Sie die Art von guten Gründen haben, sich mit Details Ihres Speicherlayouts auf niedriger Ebene und spezifisch zu befassen, die Sie dazu bringen würden, zu glauben, dass Sie überhaupt Bitfelder benötigen, werden die anderen damit verbundenen Anforderungen, die Sie haben, mit ziemlicher Sicherheit auf ihre Unterspezifikation stoßen.

(TL;DR – Wenn Sie anspruchsvoll genug sind, um Bitfelder legitim zu “benötigen”, sind sie nicht gut genug definiert, um Ihnen zu dienen.)

  • Die Autoren des Standards waren an dem Tag, an dem das Bitfeld-Kapitel entworfen wurde, im Urlaub. Also musste es der Hausmeister machen. Es gibt keine Begründung dafür irgendetwas darüber, wie Bitfelder entworfen werden.

    – Ludin

    19. Dezember 2018 um 15:00 Uhr


  • Es gibt keinen Zusammenhang technisch Begründung. Aber das lässt mich zu dem Schluss kommen, dass es eine gab politisch Begründung: um zu vermeiden, dass der vorhandene Code oder die Implementierungen falsch werden. Aber das Ergebnis ist, dass es sehr wenig über Bitfelder gibt, auf das Sie sich verlassen können.

    – Johannes Bollinger

    19. Dezember 2018 um 15:20 Uhr

  • @JohnBollinger Es gab definitiv eine Politik, die C90 viel Schaden zugefügt hat. Ich habe einmal mit einem Mitglied des Komitees gesprochen, das die Quelle vieler Scheiße erklärte – der ISO-Standard dürfe nicht bestimmte bestehende Technologien bevorzugen. Aus diesem Grund bleiben wir bei schwachsinnigen Dingen wie der Unterstützung für das 1er-Komplement und der vorzeichenbehafteten Größe und der implementierungsdefinierten Vorzeichenbelegung hängen charUnterstützung für Bytes, die keine 8 Bit sind usw. usw. Sie durften schwachsinnigen Computern keinen Marktnachteil verschaffen.

    – Ludin

    19. Dezember 2018 um 15:26 Uhr


  • @Lundin Es wäre interessant, eine Sammlung von Zuschreibungen und Post-Mortems von Leuten zu sehen, die glaubten, dass Kompromisse fälschlicherweise eingegangen wurden, und warum. Ich frage mich, wie viel Studie von diesen “Das haben wir letztes Mal gemacht, und es hat/hat nicht geklappt” ist zu institutionellem Wissen geworden, um den nächsten solchen Fall zu informieren, im Gegensatz zu nur Geschichten in den Köpfen der Menschen.

    – HostileFork sagt, vertraue SE nicht

    19. Dezember 2018 um 15:29 Uhr

  • Dies wird immer noch als Punkt Nr. aufgeführt. 1 der ursprünglichen Prinzipien von C in der C2x-Charta: „Vorhandener Code ist wichtig, vorhandene Implementierungen sind es nicht.“ … “niemand wurde als Beispiel für die Definition von C herangezogen: Es wird davon ausgegangen, dass alle bestehenden Implementierungen etwas geändert werden müssen, um dem Standard zu entsprechen.”

    – Leuschenko

    19. Dezember 2018 um 16:00 Uhr

Benutzeravatar von NathanOliver
NathanOliver

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

struct mystruct { int enabled:1; };

erklärt enable als ein 1-Bit-Bitfeld. Da es signiert ist, sind die gültigen Werte -1 und 0. Setzen Sie das Feld auf 1 Überläufe, die etwas zurückgehen -1 (Dies ist undefiniertes Verhalten)

Im Wesentlichen, wenn es sich um ein vorzeichenbehaftetes Bitfeld handelt, ist der maximale Wert 2^(bits - 1) - 1 welches ist 0 in diesem Fall.

  • “Da es signiert ist, sind die gültigen Werte -1 und 0”. Wer hat gesagt, dass es signiert ist? Es ist kein definiertes, sondern implementierungsdefiniertes Verhalten. Wenn es signiert ist, dann sind die gültigen Werte - und +. Das 2er-Komplement spielt keine Rolle.

    – Ludin

    19. Dezember 2018 um 14:56 Uhr


  • @Lundin Eine 1-Bit-Zwei-Komplement-Zahl hat nur zwei mögliche Werte. Wenn das Bit gesetzt ist, dann ist es -1, da es das Vorzeichenbit ist. Wenn es nicht gesetzt ist, ist es “positiv” 0. Ich weiß, dass dies implementierungsdefiniert ist, ich erkläre nur die Ergebnisse anhand der häufigsten Implantation

    – NathanOliver

    19. Dezember 2018 um 15:00 Uhr

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

    – Ludin

    19. Dezember 2018 um 15:03 Uhr

  • @JohnBollinger Das verstehe ich. Deshalb habe ich den Haftungsausschluss, dass dies eine Implementierung ist. Zumindest für die großen 3 behandeln sie alle int wie in diesem Fall unterschrieben. Es ist eine Schande, dass Bitfelder so unterspezifiziert sind. Es ist im Grunde hier diese Funktion, fragen Sie Ihren Compiler, wie Sie sie verwenden.

    – NathanOliver

    19. Dezember 2018 um 15:08 Uhr


  • @Lundin, der Wortlaut des Standards für die Darstellung vorzeichenbehafteter Ganzzahlen kann den Fall, in dem Bits mit Nullwert vorhanden sind, zumindest in zwei der drei zulässigen Alternativen, problemlos handhaben. Dies funktioniert, weil es (negativ) zuweist Werte platzieren Bits zu signieren, anstatt ihnen eine algorithmische Interpretation zu geben.

    – Johannes Bollinger

    19. Dezember 2018 um 15:08 Uhr


Man könnte sich das so vorstellen, dass im 2er-Komplementsystem das Bit ganz links das Vorzeichenbit ist. Jede vorzeichenbehaftete ganze Zahl mit dem am weitesten links gesetzten Bit ist somit ein negativer Wert.

Wenn Sie eine vorzeichenbehaftete 1-Bit-Ganzzahl haben, enthält sie nur das Vorzeichenbit. Also zuordnen 1 zu diesem einzelnen Bit kann nur das Vorzeichenbit gesetzt werden. Beim Zurücklesen wird der Wert also als negativ interpretiert, ebenso wie -1.

Die Werte, die eine vorzeichenbehaftete 1-Bit-Ganzzahl enthalten kann, sind -2^(n-1)= -2^(1-1)= -2^0= -1 und 2^n-1= 2^1-1=0

Benutzeravatar von iammilind
iammilind

Gemäß der C++-Standard n4713, wird ein sehr ähnlicher Codeausschnitt bereitgestellt. Der verwendete Typ ist BOOL (benutzerdefiniert), kann aber auf jeden Typ angewendet werden.

12.2.4

4 Ob der Wert wahr oder falsch in einem Bitfeld des Typs gespeichert wird bool beliebiger Größe (einschließlich eines Ein-Bit-Bit-Felds), das Original bool Wert und der Wert des Bit-Felds sollen gleich sein. Wenn der Wert eines Enumerators in einem Bitfeld des gleichen Enumerationstyps gespeichert wird und die Anzahl der Bits im Bitfeld groß genug ist, um alle Werte dieses Enumerationstyps (10.2) aufzunehmen, werden der ursprüngliche Enumeratorwert und die Wert des Bitfeldes soll gleich sein.
[ Example:

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

— end example ]


Auf den ersten Blick erscheint der fettgedruckte Teil interpretierbar. Die richtige Absicht wird jedoch deutlich, wenn die enum BOOL leitet sich von der ab 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 obigem Code gibt es eine Warnung ohne -Wall -pedantic:

Warnung: „mystruct::enabled“ ist zu klein, um alle Werte von „enum BOOL“ aufzunehmen
struct mystruct { BOOL enabled:1; };

Die Ausgabe ist:

Ist behindert !! (beim Benutzen enum BOOL : int)

Wenn enum BOOL : int ist einfach gemacht enum BOOLdann ist die Ausgabe wie in der obigen Standardpassage angegeben:

Ist aktiviert (bei Verwendung von enum BOOL)


Daraus lässt sich, wie auch wenige andere Antworten haben, schließen int type ist nicht groß genug, um den Wert “1” in nur einem Bit-Bit-Feld zu speichern.

Benutzeravatar von ar18
ar18

An Ihrem Verständnis von Bitfeldern ist meines Erachtens nichts auszusetzen. Was ich sehe, ist, dass Sie mystruct zuerst als neu definiert haben struct mystruct {int aktiviert:1; } und dann als struct mystruct s;. Was du hättest 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");
}

1420820cookie-checkWarum gibt das Zuweisen eines Werts zu einem Bitfeld nicht denselben Wert zurück?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy