sizeof() の解説と実装リファレンス

記事内には広告が含まれています。

sizeof() の解説と実装リファレンス

【概要】

sizeof() は、CおよびC++言語(Arduinoの基盤言語)において、指定されたデータ型またはオブジェクトがメモリ上で占有するバイト数を取得するための演算子です。

重要な点として、sizeof() は実行時(Runtime)に計算される関数ではなく、コンパイル時(Compile-time)に評価される演算子です。これにより、実行時のオーバーヘッドを一切発生させずにメモリサイズを確定させることができ、リソース制約の厳しい組み込みシステムにおいて極めて重要な役割を果たします。

【仕様】

  • 構文: sizeof(type) または sizeof(expression)
  • 引数:
    • 型(Type): int, float, char, struct 等の型名を指定。
    • 式(Expression): 配列変数や構造体変数などのオブジェクトを指定。
  • 戻り値: size_t 型(符号なし整数型)。
  • データ型サイズ(Arduino環境):
    • AVR(Uno, Nano等): size_t は 16ビット(最大 65,535 バイト)。
    • ESP32, ARM Cortex-M系: size_t は 32ビット(最大 4,294,967,295 バイト)。
  • 初期状態: コンパイル時に定数として置き換えられるため、初期状態や実行状況に依存せず一貫した値を返します。

【技術的特徴】

1. コンパイル時評価

sizeof() は実行時にメモリを走査する strlen() などとは根本的に異なります。コンパイラがコードをビルドする際にサイズを確定させるため、実行速度への影響はゼロです。

2. 構造体のパディング(Padding)

構造体に対して sizeof() を適用した場合、メンバ変数の合計サイズよりも大きな値を返すことがあります。これは、CPUがメモリに効率的にアクセスするための「アライメント(境界調整)」により、コンパイラが自動的に挿入するパディング空間が含まれるためです。

3. 配列とポインタの差異

sizeof() の挙動において最も注意すべき技術的差異は、配列とポインタの扱いです。
– 配列: sizeof(array) は配列全体のバイト数を返します。
– ポインタ: sizeof(pointer) は、ポインタ変数そのものが格納するアドレスのサイズ(32bit環境なら4バイト、64bitなら8バイト)を返します。

【注意点】

  • ポインタの減衰(Pointer Decay):
    関数に配列を渡すと、配列はポインタに変換(減衰)されます。関数の引数として受け取ったポインタに対して sizeof() を使用すると、配列のサイズではなくポインタのサイズが返されるため、注意が必要です。
  • 動的メモリ確保:
    malloc() や new を使用して動的に確保したメモリ領域に対して sizeof() は使用できません。動的確保された領域のサイズを知るには、確保時にサイズを別途変数に保持しておく必要があります。
  • 割り込みの影響:
    コンパイル時演算であるため、割り込み(ISR)による影響を全く受けません。これはリアルタイム性が求められる組み込み開発において安全な動作を保証します。
  • リソース制限:
    AVRマイコン等の16ビットシステムでは、size_t の上限に注意してください。非常に巨大な構造体や配列を扱う際、サイズがオーバーフローする可能性があります。

【サンプルコード】

// 構造体の定義   ypedef struct {
  int id;
  char name[16];
  float value;
} SensorData;

void setup() {
  Serial.begin(9600);

  // 1. 型のサイズ確認
  Serial.print("Size of SensorData: ");
  Serial.println(sizeof(SensorData)); // パディングを含めたサイズを表示

  // 2. 配列のサイズ確認
  int buffer[10];
  Serial.print("Size of buffer array: ");
  Serial.println(sizeof(buffer));    // 10 * sizeof(int) の結果

  // 3. ポインタと配列の比較(注意点)
  int* ptr = buffer;
  Serial.print("Size of pointer: ");
  Serial.println(sizeof(ptr));       // ポインタ自体のサイズ(例: 2または4)

  // 4. 配列の要素数を計算する標準的な手法
  int array2[5] = {1, 2, 3, 4, 5};
  int length = sizeof(array2) / sizeof(array2[0]);
  Serial.print("Length of array2: ");
  Serial.println(length);
}

void loop() {}

【活用例】

  • バッファ管理: 通信プロトコル(UART, SPI, I2C)で送信するデータパケットのサイズを動的に計算し、正確なバイト数を送信する。
  • メモリフットプリントの監視: 構造体やグローバル変数のサイズを把握し、RAMの消費量を正確に算出して、リソースの枯渇を防ぐ。
  • 汎用的な配列操作: 配列の要素数を sizeof(arr) / sizeof(arr[0]) で取得することで、配列のサイズ変更時にループの条件式を修正する手間を省く。
  • シリアライズ処理: 構造体の全メンバを連続したメモリとしてシリアル出力する際、sizeof() を用いて正確な範囲を指定する。

コメント

タイトルとURLをコピーしました