第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 など)を使用する。
---
次の章では、厳密なエイリアシングと有効型規則について学びます。