2017-10-31 88 views
1

異なる構造体のメンバにどのように変数ポイントを持たせることができますか?これは私がやろうとしていることですが、3行目は失敗します。構造体を別の構造体メンバにキャストする方法

volatile uint8_t tx_message_buffer[sizeof(MESSAGE)]; 
struct MESSAGE *tx_message = (MESSAGE *)tx_message_buffer; 
struct PAYLOAD *tx_payload = (PAYLOAD *)tx_message->payload; 

ここに構造体定義があります。

#define MSG_MAX_PAYLOAD_LENGTH 64 

typedef struct PAYLOAD { 
    uint8_t descriptor; 
    uint8_t parameters[MSG_MAX_PAYLOAD_LENGTH-1]; 
}; 

typedef struct MESSAGE { 
    uint8_t address; 
    uint8_t length; 
    PAYLOAD payload; 
    uint8_t checksum; 
}; 
+0

サイドノート:これはシリアル化を行うには良い方法ではありません。アライメントや厳密なエイリアス違反の問題に簡単に取り組むことができます。すべての型が 'uint8_t'なのでこのコードでは起こりませんが、他の型を使うと問題が発生します。 (そして私はパディングとエンディアンの問題についてまだ言及していない)。 – user694733

+0

@ user694733詳しいことはできますか?私は問題があるかもしれないことを知っています。配列を使用して、他のどのような種類の問題を引き起こすでしょうか? 1つのタイプのすべてのメッセージ要素を収集する方法について、より良い考えがありますか? – Oystein

+0

基本的には、あるタイプのキャストポインタを使ってCの他のタイプのデータにアクセスすることはできません。これらのルールには例外がいくつかありますが、実際は地雷です。あなたは、コンパイラがハードウェア上で確実に動作しないコードを生成したり、誤ってコードを生成したりすることがないようにすることができます。バイトシフトとマスキングを使用し、結果をバイト配列に代入することで、各構造体メンバを手動でバイトに変換するのが正しい方法です(移植性と信頼性が最も高くなります)。 – user694733

答えて

2

あなたはあなたのコード内で大きな問題があります。struct MESSAGEためのストレージは、一般的にchar[]配列とは異なるアライメントの要件を持っている可能性があるため、2行目のキャストは、有効ではありません。たとえば、descriptorのタイプをuint32_tに変更すると、一部のプラットフォームで構造体全体の偶数アドレスの場所が強制される可能性があります。あなたがchar *に任意のオブジェクトのポインタを変換するために許可されているので、他の方法の周りにそれをやって

は、を通じて、有効になります:

volatile struct MESSAGE tx_message; 
volatile uint8_t *tx_message_buffer = (char*)tx_message; 

あなたがPAYLOAD構造体のポインタを取らなかったので、3行目は失敗します。 tx_message.payloadはすでに正しいタイプであるため、

struct PAYLOAD tx_payload = &tx_message.payload; 

、結果をキャストする必要はありません。あなたは変数ではないポインタを取得している、

PAYLOAD payload; 

を使用することにより

+0

かっこがなぜですか? – Elazar

+0

@Elazar私は、アドレス表現のほうがやや読みにくい引数をかっこで囲んで見ています。明らかに、 ' - >'の方が優先順位が高いので、かっこを取り除くことができます。 – dasblinkenlight

+0

エラーメッセージが表示される:「不完全な型へのポインタを参照解除する」 – Oystein

0

。意味は

message->payload; 

ポインタではありません。

ポインタを使用する必要があります。

PAYLOAD * payload; 

それとも

&message->payload; 
3

このコードは、多くの問題を抱えている構造体のアドレスを取得します。他の回答で指摘したように

  • 、あなたがPAYLOAD payload;メンバーを指すようにポインタを設定することはできません、あなたはそのアドレス、&tx_message->payloadを指すようにする必要があります。

  • typedef struct PAYLOAD {}は、typedef struct {} PAYLOADである必要があります。

  • (MESSAGE *)tx_message_bufferは完全に野生的なキャストであり、不明確に定義された動作のいくつかのケースを引き起こします。まず第一に、volatile修飾子を捨てるべきではありません。しかし、この構造体を参照解除するとすぐにstrict aliasingに違反し、未定義のビヘイビアが呼び出されます。何でも起れる。

    typedef struct { 
        uint8_t address; 
        uint8_t length; 
        PAYLOAD payload; 
        uint8_t checksum; 
    } MESSAGE; 
    
    typedef union { 
        MESSAGE message; 
        uint8_t tx_message_buffer[sizeof(MESSAGE)]; 
    } message_something; 
    

    このコードが有効と明確に定義されている。これらのポインタのバグを解決するために

    は、あなたがこれに似た何かを行うことができます。

  • データプロトコルを表すために構造体を使用することは、構造体にパディングが全く含まれていないことを保証する必要があるため、悪い習慣です。 MESSAGE構造体のメモリレイアウトは決してデータプロトコルのメモリレイアウトに対応することは決して保証されません。構造体は、特定のCPUの配置要件に適合するパディングバイトを持つことができます。

    #pragma pack(1)などの非標準Cでのパディングを無効にすることは、携帯性の要件によっては、十分でない場合もあります。完全な移植性を実現するには、シリアライゼーション/デシリアライゼーションルーチンを記述する必要があります。

関連する問題