2017-05-24 1 views
3

Cでの予期せぬ結果:私は131809282883593 int型8バイト長の長いを復号化するために割り当てを実装しましたビットフィールドと連合 - 私はCコースには、次の割り当てを与えてきた

enter image description here

次のように:

#include <stdio.h> 
    #include <string.h> 

    struct Message { 
     unsigned int hour : 5; 
     unsigned int minutes : 6; 
     unsigned int seconds : 6; 
     unsigned int day : 5; 
     unsigned int month : 4; 
     unsigned int year : 12; 
     unsigned long long int code : 26; 
    }; // 64 bit in total 

    union Msgdecode { 
     long long int datablob; 
     struct Message elems; 
    }; 

    int main(void) { 

     long long int datablob = 131809282883593; 
     union Msgdecode m; 

     m.datablob = datablob; 

     printf("%d:%d:%d %d.%d.%d code:%lu\n", m.elems.hour, m.elems.minutes, 
     m.elems.seconds, m.elems.day, m.elems.month, m.elems.year,(long unsigned int) m.elems.code); 

     union Msgdecode m2; 
     m2.elems.hour = 9; 
     m2.elems.minutes = 0; 
     m2.elems.seconds = 0; 
     m2.elems.day = 30; 
     m2.elems.month = 5; 
     m2.elems.year = 2017; 
     m2.elems.code = 4195376; 

     printf("m2.datablob: should: 131809282883593 is: %lld\n", m2.datablob); //WHY does m2.datablob != m.datablob?! 
     printf("m.datablob: should: 131809282883593 is: %lld\n", m.datablob); 

     printf("%d:%d:%d %d.%d.%d code:%lu\n", m2.elems.hour, m2.elems.minutes, 
      m2.elems.seconds, m2.elems.day, m2.elems.month, m2.elems.year, (long unsigned int) m2.elems.code); 

    } 

TRY IT ONLINE.

を私のハードを与える..what時間は出力です。これまでデコード/エンコーディングはうまくいきました。 9:0:0 2017年5月30日とコード4195376はを期待されているが、「datablob」の差は本当にない - そしてそれが由来どこ理由/私が把握することはできません。

9:0:0 30.5.2017 code:4195376 
m2.datablob: should: 131809282883593 is: 131810088189961 
m.datablob: should: 131809282883593 is: 131809282883593 
9:0:0 30.5.2017 code:4195376 

あなたが見ることができるように、データブロックはであり、元に戻ってになりますが、オリジナルはありません。私はこれについてCに慣れ親しんだ同僚に相談しましたが、この動作の理由を理解できませんでした。

Q:なぜブロブが異なるのですか?

ボーナス-Q:別のフィールドを含めるために労働組合Msgdecodeを操作すると、奇妙なことが起こります:

union Msgdecode { 
    long long int datablob; 
    struct Message elems; 
    char bytes[8]; // added this 
}; 

結果:

9:0:0 30.5.2017 code:0 
m2.datablob: should: 131809282883593 is: 8662973939721 
m.datablob: should: 131809282883593 is: 131809282883593 
9:0:0 30.5.2017 code:4195376 

をPS:ビットフィールド+組合の質問についてはSOに読ん彼らがむしろ信頼できないという印象を与えました。これは一般的に言えますか?

+2

これがあなたの割り当てであれば、それはひどいものです。ビットフィールド構造はレイアウトに関してほとんど保証しません。シフトとマスキングを使用してください!そして、 'int'、' unsigned int'、 '_Bool'ビットフリッド以外を使うことは保証されていません。脇役として:あなたが使用するタイプのどれも、spicifの表現や幅を保証しません!固定幅が必要な場合は、固定幅タイプを使用してください。 – Olaf

+0

@Olafそれは私の割り当てです。最終的に私のPS-Questionは「はい」と答えることができますか? :) – Gewure

+0

はい、私たちは個人指導のサービスではありません。その家庭教師と良いCの本を取得し、本の情報でコースの材料をフィルタリングします。脇役として:私は決して私の生徒にそのような課題を与えませんでした:コードで:あなたは私のコースで失敗するでしょう。いくつかの理由:上記を参照してください。 – Olaf

答えて

3

にビットフィールドのレイアウトを他のget*()関数の実装を残しておきますそれらは実装定義されています。 C standardのセクション6.7.2.1から

:実装はbit-フィールドを保持するのに十分な大きさの任意のアドレス指定可能な記憶部を割り当てることができる

。十分なスペースが残っている場合は、構造内の別のビットフィールドの直後にあるビットフィールド は、同じユニットの隣接ビットにパックされた になります。スペースが不十分な場合は、 が残り、適合しないビットフィールドが に置かれるか、隣接ユニットがオーバーラップするかは、実装定義の です。 単位(上位から下位、または下位から上位へ)のビットフィールドの割り当て順序は、実装定義の です。アドレス指定可能な記憶装置 ユニットのアライメントは不定です。

これは、標準に準拠した方法でレイアウトに頼ることができないことを意味します。

これは、この特定のケースでビットがどのように配置されているかを見てみましょう。繰り返すには、ここからすべてが実装定義の動作の領域にあるです。私たちは、m2.datablobが8662973939721である2番目のケースから説明してみましょう。で個別に各フィールドに割り当てるm2、その後blobvalueに割り当てる、今度は、BLOB値を見てみましょう

- hour:  9: 0 1001 (0x09) 
- minutes: 0: 00 0000 (0x00) 
- seconds: 0: 00 0000 (0x00) 
- day:  30: 11 1110 (0x3E) 
- month:  5: 0101 (0x05) 
- year: 2017: 0111 1110 0001 (0x7e1) 
- code: 4195376: 00 0100 0000 0000 0100 0011 0000 (0x0400430) 

最初m

まずあなたがm2に割り当てる値のビット表現を見てみましょう上記の値:

131809282883593 0x77E13D7C0009      0111 0111 1110 0001 
            0011 1101 0111 1100 0000 0000 0000 1001 

    8662973939721 0x07E1017C0009      0000 0111 1110 0001 
            0000 0001 0111 1100 0000 0000 0000 1001 

我々は右からの値を見ることで起動した場合は、私たちが値9を参照することができ、左に行くので、私たちの最初の5ビットがあります。次に、次の2つのフィールドのための6つのゼロビットの2つのセットがある。その後、30、次に5のビットパターンが表示されます。
さらに少し上に、値2017のビットパターンがありますが、この値と前の値の間には6ビットがゼロに設定されています。

  year  ??? month day sec  min hour 
     ------------ ----- --- ---- ------ ----- ----- 
    |   | |  || || ||  ||  |  | 
0000 0111 1110 0001 0000 0001 0111 1100 0000 0000 0000 1001 

のでyearmonthフィールドの間にいくつかのパディングがあります。だから、次のようなレイアウトがあるように見えます。 mm2の表現を比較すると、差異はmonthyearの間の6ビットのパディングと、yearの左側の4ビットです。

ここに表示されていないものは、codeフィールドのビットです。だから構造体の大きさはどれくらいですか?

我々は、コードにこれを追加した場合:

printf("size = %zu\n", sizeof(struct Message)); 

我々が得る:

size = 16 

それは我々が思ったよりもかなり大きいです。だから、bytesの配列unsigned char [16]を作り、出力しましょう。コード:

int i; 
printf("m: "); 
for (i=0; i<16; i++) { 
    printf(" %02x", m.bytes[i]); 
} 
printf("\n"); 
printf("m2:"); 
for (i=0; i<16; i++) { 
    printf(" %02x", m2.bytes[i]); 
} 
printf("\n"); 

出力:

m: 09 00 7c 3d e1 77 00 00 00 00 00 00 00 00 00 00 
m2: 09 00 7c 01 e1 07 00 00 30 04 40 00 00 00 00 00 

今、私たちは、平方メートルの表現でコードフィールドに対応する0x0400430のビットパターンを参照してください。このフィールドの前にはさらに20ビットのパディングがあります。また、バイトは、リトルエンディアンマシン上にあることを示す値の逆の順序になっています。値がどのように配置されるかを考えると、各バイト内のビットもリトルエンディアンである可能性があります。

なぜパディングですか?これはアライメントに関連している可能性が最も高いです。最初の5つのフィールドは8ビット以下で、それぞれが1バイトに収まることを意味します。 1バイトのアラインメント要件はないため、パックされます。次のフィールドは12ビットで、16ビット(2バイト)フィールドに収まる必要があります。したがって、6ビットのパディングが追加されるので、このフィールドは2バイトのオフセットで開始されます。次のフィールドは26ビットで、32ビットのフィールドが必要です。これは、4バイトのオフセットで開始し、4バイトを使用する必要があることを意味しますが、このフィールドはunsigned long longと宣言されています(この場合は8バイトです)。フィールドは8バイトを使い切ります。あなたはこのフィールドを宣言していたのですか?unsigned intこれはおそらく同じオフセットで開始されますが、8の代わりに4バイト以上を使います。

ブロブ値が131810088189961の最初のケースはどうですか?

131809282883593 0x77E13D7C0009      0111 0111 1110 0001 
            0011 1101 0111 1100 0000 0000 0000 1001 

131810088189961 0x77E16D7C0009      0111 0111 1110 0001 
            0110 1101 0111 1100 0000 0000 0000 1001 

これらの2つの表現は、データを格納するビットで同じ値が設定されていますのは、1の「期待」に比べてその表現を見てみましょう。それらの違いは、monthyearフィールドの間の6つのパディングビットにあります。なぜこの表現が異なるかについては、コンパイラはおそらく、あるビットが読み書きできない、または書き込めなかったことを認識したときに、いくつかの最適化を行ったと思われます。ユニオンにchar配列を追加することによって、それらのビットを読み書きすることができ、最適化ができなくなる可能性があるためです。

gccでは、__attribute((packed))を構造体に使用できます。

size = 8 
9:0:0 30.5.2127 code:479 
m2.datablob: should: 131809282883593 is: 1153216309106573321 
m.datablob: should: 131809282883593 is: 131809282883593 
9:0:0 30.5.2017 code:4195376 
m: 09 00 7c 3d e1 77 00 00 
m2: 09 00 7c 85 1f 0c 01 10 

ビット表現:

1153216309106573321 0x10010C1F857C0009 0001 0000 0000 0001 0000 1100 0001 1111 
             1000 0101 0111 1100 0000 0000 0000 1001 

    131810088189961 0x77E16D7C0009  0000 0000 0000 0000 0111 0111 1110 0001 
             0110 1101 0111 1100 0000 0000 0000 1001 

しかし、たとえこれにより、you could run into issuesそれを行うと(印刷時にループの制限と共に8にbytesアレイを調整した後)を次の出力を与えます。

要約すると、ビットフィールドではレイアウトの保証はありません。あなたはビットシフトとマスキングを使って、オーバーレイしようとするのではなく、ビットフィールド内外の値を取得する方がよいでしょう。

+0

非常に詳細な答えをありがとう、@ダッシュ私は非常にそれをaprreciate。私はこれを別のマシンに複製しようとしましたが、若干異なる結果が得られました。これはかなり啓発的なものでした。 私はビットフィールドを使用しない理由を知っています。 – Gewure

1

ここでの問題は、あなたがライン37にある:

m2.elems.code = 4195376; 

あなたはビットフィールドに無効なタイプを割り当て:

struct Message { 
    unsigned int hour : 5; 
    unsigned int minutes : 6; 
    unsigned int seconds : 6; 
    unsigned int day : 5; 
    unsigned int month : 4; 
    unsigned int year : 12; 
    unsigned long long int code : 26; <-- invalid 
}; 

参照:トピックでhttps://www.tutorialspoint.com/cprogramming/c_bit_fields.htmビットフィールドを宣言

ここにはあなただけが使用できると言われていますint,は、と符号なし整数をタイプとして符号付きします。

私はコンパイラがm2.elems.codeをintと解釈し、max intより大きな割り当てで正確に何をするのか分かりません。

1

繰り返すが、ビットフィールド構造体内のビットのレイアウトは保証されない(つまり、コンパイラに依存する)ため、この種のビット操作は適切ではない。このような機能を実現するには、代わりにビット操作を使用する必要があります。

本の簡単な例は次のようになります。

#define HOUR_BIT_START 59 // The bit number where hour bits starts 
#define HOUR_BIT_MASK 0x1F // Mask of 5 bits for the hour bits 

unsigned int getHour(long long int blob) 
{ 
    return (unsigned int)((blob >> HOUR_BIT_START) & HOUR_BIT_MASK); 
} 

int main (int argc, char *argv[]) 
{ 
    unsigned long long int datablob = 131809282883593; 

    printf("%d:%d:%d %d.%d.%d code:%lu\n", getHour(datablob), getMinutes(datablob), getSeconds(datablob), getDay(datablob), getMonth(datablob), getyear(datablob), getCode(datablob)); 
} 

は私が間に存在する可能性があるstructおよび任意のパディング以内にご

関連する問題