第5章:処理系定義動作と移植性

この章で学ぶこと

  • 処理系定義動作と未規定動作の違い
  • 主要な処理系定義動作
  • 移植可能なコードの書き方
  • プラットフォーム差異への対処

---

5.1 動作の分類

処理系定義動作(Implementation-Defined)

実装が選択を文書化する義務がある動作。

未規定動作(Unspecified)

実装が選択を文書化する義務がない動作。

比較

| 種類 | 文書化義務 | 一貫性 | |------|-----------|--------| | 処理系定義 | あり | 同一実装では一貫 | | 未規定 | なし | 不明 |

---

5.2 主要な処理系定義動作

基本型のサイズ

sizeof(int);    /* 処理系定義(通常4) */
sizeof(long);   /* 処理系定義(4または8) */

charの符号

char c = -1;  /* charが符号付きか符号なしかは処理系定義 */

移植性のある書き方:

signed char sc = -1;
unsigned char uc = 255;

右シフト(負の値)

int x = -1;
int y = x >> 1;  /* 論理シフトか算術シフトかは処理系定義 */

ビットフィールドの配置

struct {
    int a : 4;
    int b : 4;
};
/* メモリ配置は処理系定義 */

ポインタの整数変換

int *p;
uintptr_t u = (uintptr_t)p;  /* 処理系定義 */

---

5.3 未規定動作の例

関数引数の評価順序

int a = 0;
f(a++, a++);  /* 順序は未規定 */

関数呼び出しのインターリーブ

f() + g();  /* f()とg()の評価順序は未規定 */

同一変数への複数代入

*p = *q = 0;  /* 代入の順序は未規定 */

---

5.4 移植可能なコード

固定幅整数型を使う

#include <stdint.h>

int32_t x;      /* 正確に32ビット */
uint64_t y;     /* 正確に64ビット */
int_least32_t z; /* 少なくとも32ビット */

エンディアンに依存しない

/* NG: バイトオーダー依存 */
uint32_t x = *(uint32_t *)buf;

/* OK: ポータブル */
uint32_t x = ((uint32_t)buf[0] << 24) |
             ((uint32_t)buf[1] << 16) |
             ((uint32_t)buf[2] << 8)  |
             ((uint32_t)buf[3]);

構造体のサイズに依存しない

/* NG: パディングが処理系依存 */
write(fd, &s, sizeof(s));

/* OK: フィールドごとにシリアライズ */
write(fd, &s.x, sizeof(s.x));
write(fd, &s.y, sizeof(s.y));

---

5.5 プリプロセッサでの対応

#if defined(__LP64__) || defined(_WIN64)
    /* 64ビット環境 */
#else
    /* 32ビット環境 */
#endif

#if CHAR_BIT != 8
    #error "8-bit bytes required"
#endif

---

5.6 この章のまとめ

処理系定義動作の例

  • 基本型のサイズ
  • charの符号
  • 負の値の右シフト
  • ビットフィールドの配置
  • 移植性のためのベストプラクティス

  • 固定幅整数型を使用
  • エンディアンに依存しない
  • 構造体のバイナリ入出力を避ける
  • ビット演算は符号なし型で

---

確認問題

問題1

処理系定義動作と未規定動作の違いは?

解答

処理系定義動作は実装が選択を文書化する義務がある。未規定動作にはその義務がない。

問題2

移植可能なコードを書くために、int の代わりに何を使うべきですか?

解答

の固定幅整数型(int32_t, uint64_t など)を使用する。

---

次の章では、厳密なエイリアシングと有効型規則について学びます。