値をビットフィールドに割り当てても同じ値が返されないのはなぜですか?


96

このQuoraの投稿で以下のコードを見ました。

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

CとC ++の両方で、コードの出力は予想外です

無効になっています !!

あの投稿には「サインビット」に関する説明が載っていますが、どうやって設定したのか、そのまま反映されないのか分かりません。

誰かがより詳細な説明をすることはできますか?


:両方のタグそれらの標準はビットフィールドを記述するためにわずかに異なるため、必須です。C仕様およびC ++仕様の回答を参照してください。


46
ビットフィールドはint私が思うように宣言されているので、値0とのみを保持できます-1
Osiris

6
intが-1を格納する方法を考えてみてください。すべてのビットは1に設定されます。したがって、1ビットしかない場合は、明らかに-1にする必要があります。したがって、1ビットintの1と-1は同じです。チェックを「if(s.enabled!= 0)」に変更すると機能します。0にすることはできません。
ユルゲン

3
これらの規則がCとC ++で同じであることは事実です。ただし、タグの使用ポリシーによると、これはCとしてのみタグ付けし、不要な場合はクロスタグ付けを行わないでください。C ++の部分は削除します。投稿された回答には影響しません。
ランディン、

8
に変更してみましたstruct mystruct { unsigned int enabled:1; };か?
ChatterOne 2018

4
CとC ++のタグポリシー、特にコミュニティコンセンサスを通じて確立されたCとC ++の両方のクロスタグに関する部分をここで読んでください。ロールバック戦争はしませんが、この質問には誤ってC ++のタグが付けられています。さまざまなTCのために言語に多少の違いがある場合でも、CとC ++の違いについて別の質問をしてください。
ランディン

回答:


77

標準ではビットフィールドの定義は非常に不十分です。このコードを考えるstruct mystruct {int enabled:1;};と、私たちは知りません:

  • これが占める容量-パディングビット/バイトがあり、それらがメモリ内のどこにあるか。
  • ビットがメモリのどこにあるか。定義されておらず、エンディアネスにも依存します。
  • int:nビットフィールドを符号付きと見なすか、符号なしと見なすか。

最後の部分について、C17 6.7.2.1/10は次のように述べています。

ビットフィールドは、指定されたビット数で構成される符号付きまたは符号なし整数型を持つと解釈されます125)

上記を説明する非規範的な注記:

125)上記の6.7.2で指定されているように、使用される実際の型指定子が、intまたはtypedef-nameがとして定義されているint場合、ビットフィールドが符号付きか符号なしかは、実装によって定義されます。

ビットフィールドが見なされsigned int、ビットサイズを作成する1場合、データ用のスペースはなく、符号ビット用のみです。これが、一部のコンパイラーでプログラムが奇妙な結果をもたらす可能性がある理由です。

いい練習:

  • いかなる目的にもビットフィールドを使用しないでください。
  • intあらゆる形式のビット操作に符号付き型を使用しないでください。

5
仕事では、ビットフィールドのサイズとアドレスにstatic_assertsを設定して、パディングされていないことを確認しています。ファームウェアのハードウェアレジスタにはビットフィールドを使用します。
マイケル

4
@Lundin:#define-dマスクとオフセットの醜い点は、コードがシフトとビットごとのAND / OR演算子で散らかされていることです。ビットフィールドを使用すると、コンパイラが自動的に処理します。
Michael

4
@Michael ビットフィールドを使用すると、コンパイラが処理します。まあ、それが「面倒を見る」の基準が「移植不可能」で「予測不可能」であれば問題ありません。鉱山はそれより高いです。
Andrew Henle

3
@AndrewHenle Leushenkoは、C標準自体の観点からは、x86-64 ABIに従うかどうかは実装次第であると述べています。
mtraceur 2018

3
@AndrewHenleそうですね、私は両方の点で同意します。私の要点は、ロイシェンコとの不一致は、「実装定義」を使用して、C標準によって厳密に定義されておらず、プラットフォームABIによって厳密に定義されていないものだけを参照しているという事実に帰着していると思います。 C標準だけで厳密に定義されていないものに。
mtraceur 2018

58

理解できません。どうして設定したのに、そのまま表示されないのでしょうか。

なぜコンパイルされるのか、エラーが発生するのかと尋ねていますか?

はい、理想的にはエラーが表示されます。また、コンパイラの警告を使用する場合も同様です。GCCでは、と-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;
           ^

これが実装定義であるのかエラーであるのかについての理由は、キャストの必要性が古いコードを壊すことを意味する、過去の使用法と関係があるかもしれません。この規格の作成者は、警告が関係者のたるみを拾うのに十分であると信じているかもしれません。

いくつかの規範主義を導入するために、@ Lundinのステートメントをエコーし​​ます。「どんな目的でもビットフィールドを使用しないでください。」 メモリレイアウトの詳細について低レベルで具体的に説明する理由があり、そもそもビットフィールドが必要だと考えるようになると、ほぼ確実に、他の関連する要件が仕様不足に直面することになります。

(TL; DR-ビットフィールドを合法的に「必要」とするほど洗練されている場合、それらは十分に定義されておらず、サービスを提供できません。)


15
標準の作成者は、ビットフィールドの章が設計された日は休日でした。したがって、管理人はそれを行わなければなりませんでした。理論的根拠はありませんビットフィールドの設計方法については。
ランディン

9
首尾一貫した技術的根拠はありません。しかし、それにより、既存のコードや実装を誤ったものにしないようにするという政治的根拠があったと結論づけられます。しかし、結果として、信頼できるビットフィールドについてはほとんどありません。
ジョンボリンジャー

6
@JohnBollinger確かに政治があり、それがC90に多大な損害を与えました。私はかつて、がらくたの多くの原因を説明した委員会のメンバーと話しました-ISO標準は、特定の既存の技術を支持することを許可されませんでした。これが、1の補数と符号付きの大きさのcharサポート、8以外のバイトのサポートなど、実装で定義された符号のサポートなど、モロニックなもので立ち往生している理由です。モロニックコンピューターに市場の不利益を与えることはできませんでした。
ランディン

1
@Lundinトレードオフが誤って行われたと信じていた人々からの記事や死後のコレクションを見るのは興味深いでしょう。これらの「私たちは前回それを行ったが、うまくいかなかった」という研究は、人々の頭の中の物語だけでなく、次のそのような場合を知らせるための制度的知識になったのだろうか。
HostileForkはSEを信頼してはならないと

1
これはまだポイントなしとしてリストされています。C2x憲章におけるCの元の原則の1つ:「既存のコードは重要ですが、既存の実装は重要ではありません。」...「Cを定義するための手本として、1つの実装が支持されなかった:すべての既存の実装は、規格に準拠するために多少変更する必要があると想定されています。」
Leushenko

23

これは実装定義の動作です。これを実行しているマシンは2の補数の符号付き整数を使用しint、この場合は符号付き整数として扱い、ifステートメントのtrue部分を入力しない理由を説明することを前提としています。

struct mystruct { int enabled:1; };

enable1ビットのビットフィールドとして宣言します。署名されているため、有効な値は-1および0です。フィールドを1オーバーフローしてそのビットに戻るように設定する-1(これは未定義の動作です)

符号付きビットフィールドを扱う場合、本質的に最大値である2^(bits - 1) - 1とする0。この場合には。


「署名されている場合、有効な値は-1と0です。誰が署名したと言ったのですか?これは定義されていませんが、実装定義の動作です。署名されている場合、有効な値は-および+です。2の補数は関係ありません。
ランディン

5
@Lundin 1ビットの2の補数には、2つの値しかありません。ビットが設定されている場合、それは符号ビットであるため、-1です。これが設定されていない場合、「正」0です。これは実装定義であることがわかっています。最も一般的な埋め込みを使用した結果を説明しているだけです
NathanOliver

1
ここで重要なのは、2の補数またはその他の符号付き形式は、使用可能な単一ビットでは機能できないことです。
ランディン、

1
@JohnBollingerわかりました。これが、実装が定義されていることを説明するための理由です。少なくともビッグ3についてはint、この場合すべてが署名済みとして扱われます。ビットフィールドがそれほど指定されていないのは残念です。基本的にここにあるのがこの機能です。使用方法についてはコンパイラに相談してください。
NathanOliver '19

1
@Lundin、符号付き整数の表現に関する標準の表現は、少なくとも3つの許可された選択肢のうちの2つで、ゼロ値ビットがある場合を完全にうまく処理できます。これは、アルゴリズムの解釈を与えるのではなく、符号ビットに(負の)場所の値を割り当てるため、機能します。
ジョンボリンジャー

10

2の補数システムでは、左端のビットが符号ビットであると考えることができます。したがって、左端のビットが設定された符号付き整数は負の値になります。

1ビットの符号付き整数がある場合は、符号ビットしかありません。したがって1、その単一ビットに割り当てると、符号ビットのみを設定できます。したがって、それを読み返すと、値は負の値として解釈され、-1になります。

1ビットの符号付き整数が保持できる値は-2^(n-1)= -2^(1-1)= -2^0= -12^n-1= 2^1-1=0


8

どおりC ++標準n4713、非常に類似したコードスニペットが提供されます。使用されるタイプはBOOL(カスタム)ですが、どのタイプにも適用できます。

12.2.4

4 trueまたはfalseの値boolが任意のサイズのタイプのビットフィールド(1ビットのビットフィールドを含む)に格納されている場合、元のbool値とビットフィールドの値は等しくなります。列挙子の値が同じ列挙型のビットフィールドに格納され、ビットフィールドのビット数がその列挙型のすべての値(10.2)を保持するのに十分な大きさである場合、元の列挙子の値と元の列挙子の値ビットフィールドの値は、equalと比較されます。[例:

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

—例を終了]


一見すると、太字部分は解釈のために開いているように見えます。ただし、がenum BOOLから派生すると、正しい意図が明らかになり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");
}

上記のコードでは、それなしで警告を出し-Wall -pedanticます:

警告:「mystruct :: enabled」は小さすぎて「enum BOOL」のすべての値を保持できません struct mystruct { BOOL enabled:1; };

出力は次のとおりです。

無効になっています !!(使用時enum BOOL : int

enum BOOL : intsimple enum BOOLにした場合、出力は上記の標準的なパッセージで指定されているとおりです。

有効になっている(を使用する場合enum BOOL


したがって、他のいくつかの回答が持っているように、そのタイプは単一のビットビットフィールドに値「1」を格納するのに十分な大きさではないと結論付けることができますint


0

私が見ることができるビットフィールドの理解に問題はありません。私が見るのは、最初にmystructをstruct mystruct {int enabled:1;として再定義したことです}、次いでとして構造体体mystruct S。。あなたがコーディングすべきだったのは:

#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");
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.