ctypeについて

発端

is??? (例: isprintなど)に対して負の値を与えたときの挙動 (具体的にはEUC漢字の各々のバイトをis??? に食わせたときの挙動)が LinuxとNetBSDでは違うような気がしたので、なんとなく興味を持って 調べてみました。

NetBSDの場合

is??? 系のマクロはctype.hで定義されています。

#define	isprint(c)	((int)((_ctype_ + 1)[(int)(c)] & (_P|_U|_L|_N|_B)))
_ctype_ という配列に、各々のキャラクタがどういう属性を持つのかを 格納しておいて、is??? で呼ばれたときにそのフラグをチェックするように なっています。 +1されているのはEOF(-1)を食わせたときに配列の外に アクセスしに行かないためだというのをCの256本で読んだことがあります...

このとき、冒頭にあげたように漢字の断片などを(signed)charで受けて それをそのままこういったマクロに食わせてしまうと、符号拡張→負の値が 入る→結果は不定 ということになります。

Linuxの場合

とりあえずglibc-2.2.5をもってきて調べてみました。 ctype関連はglibc-2.2.5/ctype/ 以下にあります。 細かいことを無視すると以下のような部分が該当しそうです。

(ctype.h)
enum
{
  _ISupper = _ISbit (0),        /* UPPERCASE.  */
  _ISlower = _ISbit (1),        /* lowercase.  */
  _ISalpha = _ISbit (2),        /* Alphabetic.  */
  _ISdigit = _ISbit (3),        /* Numeric.  */
  _ISxdigit = _ISbit (4),       /* Hexadecimal numeric.  */
  _ISspace = _ISbit (5),        /* Whitespace.  */
  _ISprint = _ISbit (6),        /* Printing.  */
  _ISgraph = _ISbit (7),        /* Graphical.  */
  _ISblank = _ISbit (8),        /* Blank (usually SPC and TAB).  */
  _IScntrl = _ISbit (9),        /* Control character.  */
  _ISpunct = _ISbit (10),       /* Punctuation.  */
  _ISalnum = _ISbit (11)        /* Alphanumeric.  */
};
extern __const unsigned short int *__ctype_b;   /* Characteristics.  */
# define isprint(c)     __isctype((c), _ISprint)
#define __isctype(c, type) \
  (__ctype_b[(int) (c)] & (unsigned short int) type)

(ctype-info.c)
/* Defined in locale/C-ctype.c.  */
extern const char _nl_C_LC_CTYPE_class[];

#define b(t,x,o) (((const t *) _nl_C_LC_CTYPE_##x) + o)
const unsigned short int *__ctype_b = b (unsigned short int, class, 128);
なので、
const unsigned short int *__ctype_b = (((const unsigned short int *) _nl_C_LC_CTYPE_class) + 128)
となりまして、-128までを食わせても大丈夫になっているようです。 また、確認していませんが、どうやら(signed)charの値を与えたときにも unsigned charにキャストした後に食わせたときと同じ結果になるように テーブルを対称に構成しているような感じがします。

Windowsの場合

といってもソース読めないのでMSDNの解説を 読みます。-1 から 0xffまでの値を食わすことができるようです。

考察

NetBSDのmanを読む限りでは、is???系の関数(マクロ)は、ANSI C準拠であると 表示されていますので、ANSI Cにおけるis???系関数は-1未満の値を食わせたときの 動作は不定になっているのではないかと考えられます。 参考: NEWS OSのドキュメントANSI C Rationaleの解説

不定ということは、coreを吐いてもハングアップしてもいいし、 glibcがやってるように、ユーザが意図していると思われる動作を推測して 結果を返してもいいということにはなりそうです。が、もちろんその動作には 移植性がありませんから使用者側で注意しておく必要があります。

そのほか


戻る
Zinnia (zinnia@risky-safety.org)