C言語の共用体を活用した効率的なデータパッキングとアンパッキング
C言語の共用体を用いた効率的なデータパッキングとアンパッキング
共用体は、同じバイト列を異なるデータ型として再解釈する強力でメモリ効率の良い手段を提供します。もともとは相互排他的な変数間でメモリを共有するために意図されましたが、現代のCプログラマはデータのパッキング、アンパッキング、型パンニング(type punning)に頻繁に利用します。特にシリアルプロトコルやローレベル通信を扱う際に重宝されます。
共用体がメモリを共有する仕組み
共用体のすべてのメンバは同じメモリ領域を共有します。最大のメンバが共用体のサイズを決定し、すべてのメンバは同じアドレスから始まります。
union { uint16_t word; struct { uint8_t byte1; uint8_t byte2; }; } u1;
ここでは、wordが2バイトを占有し、ネストされた構造体がその2バイトをbyte1とbyte2に分割します。レイアウトは次のように視覚化できます:

2バイトを16ビットワードにパッキング
2つの8ビット値xとyを16ビットワードに結合するには、それらを構造体メンバに代入し、その後wordメンバを読み取ります:
u1.byte1 = y; u1.byte2 = x; uint16_t packed = u1.word;
この「データパンニング」はシンプルですが、プロセッサのバイトオーダ(エンディアン)に依存します。
プロセッサのエンディアンが重要
リトルエンディアンシステムは最下位バイトを先に格納し、ビッグエンディアンシステムは最上位バイトを先に格納します。シーケンス0x01020304を考えてみましょう:

リトルエンディアンマシンで簡単なテストを実行すると:
#include <stdio.h> #include <stdint.h> int main() { union { struct { uint8_t byte1; uint8_t byte2; }; uint16_t word; } u1; u1.byte1 = 0x21; u1.byte2 = 0x43; printf("Word is: %#X\n", u1.word); return 0; }
出力: Word is: 0X4321 ― リトルエンディアン格納を確認。ビッグエンディアンターゲットでは同じコードで0X2143が出力されます。
ビット演算による代替案:エンディアン非依存のパッキング
シフトとマスクを使用すると、16ビット値を構築するポータブルな方法が得られます:
uint16_t word2 = (((uint16_t)byte3) << 8) | ((uint16_t)byte4);
両手法を比較:
#include <stdio.h> #include <stdint.h> int main() { union { struct { uint8_t byte1; uint8_t byte2; }; uint16_t word1; } u1; u1.byte1 = 0x21; u1.byte2 = 0x43; printf("Word1 is: %#X\n", u1.word1); uint8_t byte3 = 0x21, byte4 = 0x43; uint16_t word2 = (((uint16_t)byte3) << 8) | ((uint16_t)byte4); printf("Word2 is: %#X\n", word2); return 0; }
ビッグエンディアンプロセッサ上: Word1 is: 0X2143, Word2 is: 0X2143。リトルエンディアンプロセッサ上: Word1 is: 0X4321, Word2 is: 0X2143。ビット演算アプローチはアーキテクチャ間で一貫した結果を保証します。
実践例:UARTでfloatを送信
シリアルリンクは通常1バイトずつ送信します。32ビットfloatを送るには、共用体を使ってfloatを4バイトの配列として見ることができます:
union buffer { float f; struct { uint8_t byte[4]; }; };
送信側:
union buffer tx; union buffer rx; float f1 = 5.5f; tx.f = f1; for (int i = 0; i < 4; ++i) { // send tx.byte[i] over UART rx.byte[i] = tx.byte[i]; // 受信シミュレーション } printf("The received data is: %f\n", rx.f);
出力: The received data is: 5.500000。共用体はバイト表現と浮動小数点値をクリーンに分離し、コードを読みやすく保守しやすくします。

要点
- 共用体は型パンニングを可能にするが、結果はエンディアンに依存する。
- ビット演算子はポータブルでアーキテクチャ非依存のパッキングを提供する。
- 明確さと保守性の観点から、多くの開発者は組み込み文脈で依然として共用体を好む。
- 共用体は、バイト指向リンク上で任意のデータ構造を送信する際に真価を発揮する。
C言語、共用体、組み込みシステムについてさらに学ぶには、記事一覧を参照してください。
埋め込み
- 米陸軍、革新的センサーと予測分析で状態基準保全(CBM)を前進
- LitmusとOden、スマート製造向け統合IIoTソリューションを発表
- サプライチェーン可視化の鍵:データとコンテキストの重要な役割
- フリート管理の革命:AIとIoTの統合によるスマートな運用
- Nucleus RTOSのセマフォ:ユーティリティサービスとデータ構造
- CyrusOne、リアルタイムアプリケーション監視でユーザー体験を向上
- IIoTとデータ分析でEHSを変革:安全性・効率・コスト削減を実現
- 責任ある信頼できるAIの構築:説明責任、説明可能性、倫理のガイド
- フォグコンピューティング解説:IoTデータフロー変革とクラウド負荷軽減
- 産業AIoT:AIとIoTの融合でインダストリー4.0イノベーションを推進
- C言語共用体:複数データ型の効率的なメモリ管理