clang: strtol()を正しく使いたい
2025/09/05
C言語で strtol()
系を使うことがあった。
今のところ strtol
, strtoll
, strtoq
の 3つのようだ。
これの unsigned
タイプは strtoul
, strtoull
, strtouq
になる。
“q
” は u_quad_t
型で BSD OS だけなのかな。Linux OS しか手元にないので探せなかった。
strtod
だと int
とかになりそうだが、残念ながら double
の “d
” のようだ。
strtoll
、strtoull
、strtof
, strtold
を使うときはマクロかコンパイルオプションで C99 以降であることを示さないといかんだろう。
正しく使いたい
今回使いたかったのは strtoull()
だったので、それを使って書いていこう。
宣言が既に難しいと思う。
unsigned long long strtoull(
const char *restrict nptr,
char **_Nullable restrict endptr,
int base);
restrict
修飾ポインタとかなかなか使わないだろう。
コンパイラへの指示なので今回は無視する。
_Nullable
は C23 からというのを見たが深くは確認していない。
私の環境ではプロトタイプ宣言に _Nullable
は入っていなかったので無視する。
そうするとこれだけになる。
unsigned long long strtoull(
const char *nptr,
char **endptr,
int base);
base
は常識の範囲内なら問題ないので、これも無視する。
base=0
だと nptr
の始まり方で 8進数、10進数、16進数を自動判定するようだが、指定できるならした方がよかろう。
そうなると、戻り値、nptr
、endptr
だけだ。
nptr
の先頭の空白は無視されるnptr
が戻り値の型、ここではunsigned long long
を超えるようならULLONG_MAX
を返す。errno = ERANGE
になる
endptr
が非NULL なら数値に変換されない文字の先頭アドレスを返す- 変換不能なら
0
を返す
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <errno.h>
#define ARRA_SIZE(a) (sizeof(a) / sizeof((a)[0]))
int main(void)
{
uint64_t ret;
char *endptr;
int e;
const char *NPTR[] = {
"0",
"1",
"-1",
"-2",
"18446744073709551615",
"18446744073709551616",
"abc",
"0abc",
"1abc",
"-1abc",
"-2abc",
};
for (size_t i = 0; i < ARRA_SIZE(NPTR); i++) {
errno = 0;
ret = strtoull(NPTR[i], &endptr, 10);
e = errno;
printf("%-20s: ret = %lu, *endptr = \'%c\'(0x%02x), errno=%d\n", NPTR[i], ret, *endptr, *endptr, e);
}
return 0;
}
実行。
なお実行環境は WSL2 の Ubuntu 24.04.5 LTS である。
0 : ret = 0, *endptr = ''(0x00), errno=0
1 : ret = 1, *endptr = ''(0x00), errno=0
-1 : ret = 18446744073709551615, *endptr = ''(0x00), errno=0
-2 : ret = 18446744073709551614, *endptr = ''(0x00), errno=0
18446744073709551615: ret = 18446744073709551615, *endptr = ''(0x00), errno=0
18446744073709551616: ret = 18446744073709551615, *endptr = ''(0x00), errno=34
abc : ret = 0, *endptr = 'a'(0x61), errno=0
0abc : ret = 0, *endptr = 'a'(0x61), errno=0
1abc : ret = 1, *endptr = 'a'(0x61), errno=0
-1abc : ret = 18446744073709551615, *endptr = 'a'(0x61), errno=0
-2abc : ret = 18446744073709551614, *endptr = 'a'(0x61), errno=0
なおエラー値はこうなっている。
#define EINVAL 22 /* Invalid argument */
#define ERANGE 34 /* Math result not representable */
私の予想では、まったく変換されなければ errno
に EINVAL
が入っている、だったのだが違った(C99以降ではないらしい。”(not in C99)” はそういう意味なのか)。
またマイナス値もエラーにならないところを見ると、unsigned
版もそうでないのも同じようにして最後に値を加工しているのかな?
ともかく errno
は範囲外しか当てにできない。
マイナス記号などは、まあ自分でチェックするとしよう。
そうするとあとは endptr
で見分けるしかない。
“0” と “0a” を見分けて後者のように変換できない文字が入っていたらエラーにしたいなら *endptr == '\0'
でチェックすれば良いだろう。
long int
を何ビットで扱うのか?
最近は 64bit CPU の環境も多い。PC だとほぼそうだろう。
では 64bit 環境で C言語の long int
は一体何バイトになっているのか?
C言語の inttypes
以外は型はサイズがきっちり決まっていなくて環境依存になる。
なので int
などをサイズが影響しやすいところで使ってしまうと困ったことになりやすい。
ここだと LP64 とか LLP64 とかの用語で区別される。
- LP64 …
long
とポインタが 64bit - LLP64 …
long long
とポインタが 64bit
Linux 系というか Unix 系は LP64 になるそうだ。
なので unsigned long
が 64bit となり、フォーマットも "%lu"
になる。
いやぁ、さっきまで long long
だけが 64bit だと思ってたよ。
自信がないので uint64_t
とビット幅で指定していたのだが、フォーマットでそっち系を使うとゴチャゴチャして書きづらいのだ。
Windows 系が LLP64 らしいが、その影響ではないな。