第8章:標準ライブラリの仕様詳細
この章で学ぶこと
- 標準ライブラリの保証と制約
- errno の正しい使い方
- シグナルハンドラの制限
- restrict 修飾子の意味
- 未定義動作を引き起こすライブラリ呼び出し
---
8.1 ライブラリ関数の制約
制約違反
ライブラリ関数に不正な引数を渡すと、通常は未定義動作です。
memcpy(NULL, src, n); /* 未定義動作 */
strlen(NULL); /* 未定義動作 */
printf("%d", 3.14); /* 未定義動作 */
「shall」の意味
規格でライブラリ関数の引数に「shall」が使われている場合、違反は未定義動作です。
> The memcpy function copies n characters from the object pointed to by s2 into the object pointed to by s1. If copying takes place between objects that overlap, the behavior is undefined.
---
8.2 errno
正しい使い方
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
errno = 0; /* 呼び出し前にクリア */
double d = strtod(str, &endptr);
if (errno == ERANGE) {
/* オーバーフローまたはアンダーフロー */
}
注意点
&errno は有効/* NG: errno の値だけでエラー判定 */
strtol(str, &endptr, 10);
if (errno != 0) { /* 危険 */ }
/* OK: 戻り値と errno の組み合わせ */
errno = 0;
long val = strtol(str, &endptr, 10);
if (endptr == str || errno != 0) { /* エラー */ }
---
8.3 シグナルハンドラの制限
安全でない操作
シグナルハンドラ内では、多くの操作が未定義動作です。
/* NG: 非async-signal-safe関数 */
void handler(int sig) {
printf("Signal %d\n", sig); /* 未定義動作 */
malloc(100); /* 未定義動作 */
}
async-signal-safe 関数
規格で明示的に許可された関数のみ使用可能:
abort()_Exit()signal() (限定的)atomic_signal_fence()安全なパターン
volatile sig_atomic_t flag = 0;
void handler(int sig) {
flag = 1; /* OK */
}
int main(void) {
signal(SIGINT, handler);
while (!flag) {
/* メインループ */
}
}
---
8.4 restrict 修飾子
標準ライブラリでの使用
void *memcpy(void * restrict s1, const void * restrict s2, size_t n);
void *memmove(void *s1, const void *s2, size_t n); /* restrict なし */
意味
restrict は「このポインタを通じてのみアクセス」の約束。
/* memcpy: オーバーラップ禁止 */
memcpy(dst, src, n); /* dst と src がオーバーラップすると未定義 */
/* memmove: オーバーラップ許可 */
memmove(dst, src, n); /* 常に安全 */
restrict 違反
char arr[10] = "hello";
memcpy(arr + 1, arr, 5); /* 未定義動作 */
memmove(arr + 1, arr, 5); /* OK */
---
8.5 NULL ポインタと長さ 0
多くの関数で未定義
memcpy(dst, NULL, 0); /* 未定義動作 */
memcpy(NULL, src, 0); /* 未定義動作 */
strlen(NULL); /* 未定義動作 */
事前チェック
if (n > 0 && src != NULL && dst != NULL) {
memcpy(dst, src, n);
}
---
8.6 文字列関数
終端の保証
char buf[10];
strncpy(buf, "hello world", sizeof(buf));
/* buf は終端されない可能性! */
buf[sizeof(buf) - 1] = '\0'; /* 明示的に終端 */
snprintf の戻り値
int ret = snprintf(buf, sizeof(buf), "value: %d", x);
/* ret: 書き込まれるはずだった文字数(終端除く) */
if (ret >= sizeof(buf)) {
/* 切り捨てが発生 */
}
strlcpy / strlcat(非標準)
/* BSD拡張、POSIX 2024で標準化予定 */
size_t ret = strlcpy(dst, src, sizeof(dst));
if (ret >= sizeof(dst)) {
/* 切り捨て */
}
---
8.7 この章のまとめ
標準ライブラリの注意点
---
確認問題
問題1
memcpy と memmove の違いは?
解答
memcpy はオーバーラップする領域では未定義動作。memmove はオーバーラップを正しく処理する。
問題2
シグナルハンドラ内でprintf を呼ぶとどうなりますか?
解答
未定義動作。printf は async-signal-safe ではない。
---
次の章では、コンパイラ拡張と標準適合について学びます。