clang: strtol()を正しく使いたい
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 らしいが、その影響ではないな。