2016-11-07 6 views
13

ArduinoからSTM32へI2Cによってデータを送信することです。CおよびC++で列挙型の構造体が異なるのはなぜですか?

だから私は、構造体と列挙型は、C++を使用してArduinoの中で定義されました:他の側では

enum PhaseCommands { 
    PHASE_COMMAND_TIMESYNC = 0x01, 
    PHASE_COMMAND_SETPOWER = 0x02, 
    PHASE_COMMAND_CALIBRATE = 0x03 
}; 

enum PhaseTargets { 
    PHASE_CONTROLLER = 0x01, 
    // RESERVED = 0x02, 
    PHASE_LOAD1 = 0x03, 
    PHASE_LOAD2 = 0x04 
}; 

struct saatProtoExec { 
    PhaseTargets target; 
    PhaseCommands commandName; 
    uint32_t  commandBody; 
} phaseCommand; 

uint8_t phaseCommandBufferSize = sizeof(phaseCommand); 

phaseCommand.target = PHASE_LOAD1; 
phaseCommand.commandName = PHASE_COMMAND_SETPOWER; 
phaseCommand.commandBody = (uint32_t)50; 

を、私は同じことがC使用して定義されました:

typedef enum { 
    COMMAND_TIMESYNC = 0x01, 
    COMMAND_SETPOWER = 0x02, 
    COMMAND_CALIBRATE = 0x03 
} MasterCommands; 

typedef enum { 
    CONTROLLER = 0x01, 
    // RESERVED = 0x02, 
    LOAD1 = 0x03, 
    LOAD2 = 0x04 
} Targets; 

struct saatProtoExec { 
    Targets   target; 
    MasterCommands commandName; 
    uint32_t  commandBody; 
} execCommand; 

uint8_t execBufferSize = sizeof(execCommand); 

execCommand.target = LOAD1; 
execCommand.commandName = COMMAND_SETPOWER; 
execCommand.commandBody = 50; 

そして私は、この構造体を比較するバイト・バイト単位:

===================== 
BYTE | C++ | C 
===================== 
Byte 0 -> 0x3 -> 0x3 
Byte 1 -> 0x0 -> 0x2 
Byte 2 -> 0x2 -> 0x0 
Byte 3 -> 0x0 -> 0x0 
Byte 4 -> 0x32 -> 0x32 
Byte 5 -> 0x0 -> 0x0 
Byte 6 -> 0x0 -> 0x0 
Byte 7 -> 0x0 -> 0x0 

なぜバイト1と2が異なるのですか?

+3

私は、サイズ+パディングを列挙すると言っています... –

+4

同じコンパイラは両方の同じフラグに使用されていますか?両方の標準では、列挙型の場合はサイズについて何も言わないので、それらが異なることが許されます。まだ1つのコンパイラで同じにする必要があります –

+0

@NorbertLangeいいえ、コンパイラは異なります。 1つはArduinoネイティブ、2つ目はKEIL MDK-ARM – Bulkin

答えて

24

これは本当に悪い考えです。

Cの2つの実装の間で同じ構造のバイナリ表現に頼るべきではありません。

構造の外部表現のバイトレベルで制御するには、適切なシリアライズ/デシリアライズコードを実行する必要があります。

つまり、パディングが原因である可能性があります。外部リンクを介してパディング(これはホストCPUを幸せに保つためにコンパイラによって追加されたものです)を送信することは、このアプローチがどれほど壊れているかのもう一つの兆候です。

+0

はい、あなたは正しいです。私はそう思っていましたが、それについての適切な意見を得たいと思います。 – Bulkin

9

Cバージョンでは、それは明らかにsizeof(Targets) == 1です。そして、構造体の2番目のフィールドが2バイト整列されているように見えます。そのため、未定義の内容のパディングバイトがあります。

ここで、C++では、sizeof(PhaseTargets)1または2です。 1(おそらく)すべてがうまくて、同じパディングスペースを持っていれば、ちょうど別のガベージ値を持つことになりました。それが2なら...間違った列挙値を持つでしょう!

構造体を簡単に初期化するには、変数の定義が必要です。値がまだない場合は、0を追加するだけですべての構造体が0に初期化されます。それでも再生できない場合は

struct saatProtoExec execCommand = {0}; 

、あなたが使用する前にそれをゼロにmemset()することができます。

移植可能な代替方法は、structのフィールドを適切なサイズの整数として宣言し、enumタイプを定数のコレクションと同じように使用することです。

struct saatProtoExec { 
    uint8_t   target; 
    uint8_t   commandName; 
    uint8_t   padding[2]; 
    uint32_t  commandBody; 
} execCommand; 
+0

さて、良いアイデアです。私はそれを他の方法で使用します。しかし、パディングなしでデータを送信し、受信者で解析するほうが良いように見えます。 – Bulkin

+2

この代替案は、同じエンディアンと32ビットのアライグメントまたはそれ以下のアーキテクチャー(この特定の質問の場合のように思われる)に対してのみ移植可能です。しかし、[巻き戻しの答え](http://stackoverflow.com/a/40465574/3951057)で指摘されているようなシリアル化を使用することは、実際にはポータブルなソリューションであり、帯域幅を効率的にすることもできます。 –

+1

その構造体は移植可能かもしれないという戦闘機会を与えますが、保証はありません。 4つの64ビットワードとして簡単に実装できます。 – gnasher729

2

すでに述べたように、特定のフォーマットの構造に頼るべきではありません。ただし、シリアライゼーションの代わりに構造体を使用すると、コンバージョンが不要で、表現が特定のアーキテクチャで実行時に互換性があり、効率的である場合に効率的になる場合があります。

構造を使用している場合このように、ここにいくつかのアドバイスです:

  • は、適切なプラグマを使用するか、レイアウトがプロジェクトオプションに依存しないことを確実にするための属性。
  • 最終的なサイズが期待どおりであることを検証するチェックを追加します。C++では、static_assertを使用できます。
  • また、endiannessが期待されるものであることを確認してください。
  • フォーマットを確認するための単体テストも良い考えです。いずれの場合においても

、私はまた、あなたの一般的なコード内および直列化のために同じstructの使用を避けることをお勧めします。時には、シリアル化されていないメンバーや、調整を行うメンバーを追加すると便利です。

さらに重要なことは、フィールドを追加して古いデータを変換する必要があるか、いくつかのバグが見つかる可能性があり、データを修正する必要があるという事実を計画することです。

古いソフトウェアで新しいファイルを開くとどうなるかを検討することもできます。多くの場合、おそらく却下されるでしょう。

+0

さて、はい。今のところ私はStructを伝えるアイディアを残しています。私は受信者が期待するように、それぞれ4バイトの2つの部分でデータを送信します。これにより、将来同じSTM32を送信者として使用するという目標が与えられ、同じプラットフォームとコンパイラで作成されたStructを送信することができます。 – Bulkin

関連する問題