そもそもBIT(3) ってなによ

これは世界七不思議のひとつといってよかったのだがプログラミング幻語 C には長らく二進数リテラルが存在しなかったのである。

いやまあ長年 gcc は独自に Binary Constants としてサポートしてたし C++14 で言語仕様となり C23 でも使えるようになったのだけど。

そのため某 N では二進数リテラルの代替として BIT(3) というマクロが存在するのだ。

__BIT(0)

これは1ビット目(ゼロオリジンだからね)に 1 を建てた数値を返す、二進数リテラルで表記するのであれば

0b1

と同じ表現が得られるわけ、また特定の範囲にビットを建てるマクロとして

__BITS(0,15)

というのもある、これは1〜16ビット目に 1 を建てた数値を返すので

0b1111111111111111

と同じ表現となる。

何に使うのこれ

二進数リテラルや BIT(3) を使うメリットはエンディアンを考慮する必要が無い事である、例えば

0b0111111111111111

という数値はリトルエンディアンであれば

0x7fff

なんだけれどもビッグエンディアンなら

0xff7f

と解釈されてしまうのでお外の世界に出る時は変換が必要となる。

ネットワークプログラミングの世界では byteorder(3) あるいは byteorder(9) という関数群を使って変換を行ってたんだが、いかんせん定数には使えないので

#if _BYTE_ORDER == _LITTLE_ENDIAN
#define FOO	0x7fff
#else /* _BYTE_ORDER == _BIG_ENDIAN */
#define FOO	0xff7f
#endif

#ifdef 切るしかなかったのだ、これでは可読性も落ちるしミスが混入する確率も高くなる、そこでこの BIT(3) を使って

#define FOO __BITS(0,14);

と表記すればスッキリするよねっていうアイデアなのだ。

ぱっと見慣れないとちょっと意味不明なコードだけどこれは名前が悪いな BINARY(3) の方が直観的だった思う。

BIT(3)の限界

ところが BINARY(3) を名乗ろうにも __BITS(m, n) に大きな弱点がある。

というのも m + 1 から n + 1 番目のビットを 1 にすることは可能なんだけども

0b10101010

みたいな穴あき表現はどうあがいても実現できないのですわ、よって

__BIT(1)|__BIT(3)|__BIT(5)|__BIT(7)

__BIT で論理和するしかないのだ、なかなか見苦しいコードで __int128__int256 時代にはもう無理ィとなる。 いやまぁ 128bit の二進数リテラルもアレではあるが…

0b10101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010

うーんこの、二進数リテラルの複数行表記が待たれますね…ただまぁ BIT(3) のように

__BIT(1)|
__BIT(3)|
__BIT(5)|
__BIT(7)|
__BIT(9)|
__BIT(11)|
__BIT(13)|
__BIT(15)|
__BIT(17)|
__BIT(19)|
__BIT(21)|
__BIT(23)|
__BIT(25)|
__BIT(27)|
__BIT(29)|
__BIT(31)|
__BIT(33)|
__BIT(35)|
__BIT(37)|
__BIT(39)|
__BIT(41)|
__BIT(43)|
__BIT(45)|
__BIT(47)|
__BIT(49)|
__BIT(51)|
__BIT(53)|
__BIT(55)|
__BIT(57)|
__BIT(59)|
__BIT(61)|
__BIT(63)|
__BIT(65)|
__BIT(67)|
__BIT(69)|
__BIT(71)|
__BIT(73)|
__BIT(75)|
__BIT(77)|
__BIT(79)|
__BIT(81)|
__BIT(83)|
__BIT(85)|
__BIT(87)|
__BIT(89)|
__BIT(91)|
__BIT(93)|
__BIT(95)|
__BIT(97)|
__BIT(99)|
__BIT(101)|
__BIT(103)|
__BIT(105)|
__BIT(107)|
__BIT(109)|
__BIT(111)|
__BIT(113)|
__BIT(115)|
__BIT(117)|
__BIT(119)|
__BIT(121)|
__BIT(123)|
__BIT(125)|
__BIT(127)

と書く方がよっぽど地獄ではある(改行はできるが)。

なお実装の都合上 uintmax_t を超える数値を扱えないので実際には __int128__int256 を扱うことはできないので注意。

NBBY 問題

そもそも実装も個人的にはイマイチなんだよね、シンプルではあるんだけど。

/* __BIT(n): nth bit, where __BIT(0) == 0x1. */
#define __BIT(__n)      \
    (((uintmax_t)(__n) >= NBBY * sizeof(uintmax_t)) ? 0 : ((uintmax_t)1 << (uintmax_t)(__n)))

/* __BITS(m, n): bits m through n, m < n. */
#define __BITS(__m, __n)        \
        ((__BIT(MAX((__m), (__n)) + 1) - 1) ^ (__BIT(MIN((__m), (__n))) - 1))

まず困るのが NBBY という定数マクロ、こいつは

NumBer of BYte

の略(おそらく)でこのアーキテクチャにおいて 1byte が何 bit なのかを定義したもの。

世の中には PDP-10 のように 1byte が 9bit の世界もあるのだがなんせ ISO/IEC 80000-13 が byte = octet と勝手に決めてしまった。

その説に沿うならもはや NBBY は常に 8 でしかありえず定数にする意味は可読性以外に無いんだよね。

よってこいつの存在自体が歴史的なものでソース互換性の為に残されてるだけ。

なので UTF-9 の世界で生きたい酔狂は NBBY ではなく CHAR_BIT を使えそもそも C 標準マクロだし移植性に困らんぞって話なのだ。

しかし世の中には標準かどうかより CHAR_BIT よりも NBBY の方がタイプ量が少ないという理由で選ぶ人も 実在 するんですね、それなら 8 の方が圧倒的にタイプ量少ないのでそっち使っえばいいのでは…?

さらに NBBY が定義される <sys/types.h> も移植性が無い上に <sys/cdefs.h> との関係も循環参照になるのでよろしくない。

MIN/MAX 問題

そんでお次、<sys/param.h>MIN/MAX に依存しているのも困る、やっぱり移植性も無いし循環参照になるしな。

こいつもまた

/* Macros for min/max. */
#define MIN(a,b)	((/*CONSTCOND*/(a)<(b))?(a):(b))
#define MAX(a,b)	((/*CONSTCOND*/(a)>(b))?(a):(b))

程度のものだからコピーすればいいだけではあるんだけど。

実際 N HEAD では このコミットで 自前で __MIN/__MAX を定義することで <sys/param.h> 依存無くしている。 まぁマニュアルの方は修正し忘れてるようだが。

uintmax_t 問題

さらに困るのが uintmax_t という C99 で導入された比較的新しい(年寄りには 1999 年はつい最近だからな)存在の型にキャストしてるのは矛盾よな。 C89 でも動くコードをお好みなのならいっそ unsigned long long int 使えばいいのだ。

ちなみに 過去記事 で書いた(過去記事?探してみろこの身の恥の全てをそこに置いてきた) __UNCONST マクロでは intptr_t 使いたくないから long int 使ってしまった結果 128bit ポインタ時代には書換が必要となるコードもあるので uintmax_t 使うのは間違いではない。

ところが現状 C の仕様を考えてる連中が真剣に intmax_t を亡き者にして intbasicmax_t/intwidest_t を導入しようとしてる現状(詳しくは N2465 を読め)ではむしろ unsigned long long int の方がマシなのでは?感はある。

なんで

  • ABI 互換を優先して(わかる)
  • その代わりにソース互換を破壊する(わからん)

ような標準化プロセスが動いてるんですかね…

あと常に uintmax_t にキャストするの 64bit が当たり前になった今では忘れがちなんだけど、32bit だとレジスタサイズよりおっきいから場合によっては性能面でペナルティ大きいんすわ。

かといって

#define __BIT_TYPE(__n, __type)      \
    (((__type)(__n) >= NBBY * sizeof(__type)) ? 0 : ((__type)1 << (__type)(__n)))

と型情報を教えて最適化を図るってのもあまり美しくはない。

この話続けると _Generics とか constexpr の教徒が活気づくでヤメヤメ、いや二進数リテラル書きたいだけでそれはもう実装されてるからおめえじゃねえ座ってろ。

<sys/cdefs.h> 問題

つーかそもそも <sys/cdefs.h> ってのはあくまで

  • pcc から gcc への移行時にしばしば発生するコンパイラの方言
  • K&R から ANSI-C への移行時および ISO-C の C99 や C11 など仕様改定にともなう差異
  • a.out から ELF へオブジェクトフォーマットを移行した事にともなう差異

を吸収し移植性を高める存在(これも 過去記事 書いたがアーカイブ化めんどくさいから放置中)だ。

なのでエンディアンの差を吸収する BIT(3) をここに置くのは違うよね <sys/endian.h> にあるならまだ理解できるんだけど。

普通の発想なら新規に <sys/bit.h> とか用意するよね、そうすりゃ F の bitset(9) みたいな機能もそこに置けるし。

それに独立したヘッダファイルにしときゃ BIT(3) 自体の移植性が問題になった時スィと持っていけるしな。

結論

結局のところ二進数リテラルを C 標準では無いからといってオレオレ二進数リテラルもどきをでっちあげるよりは 広く使われてる gcc の二進数リテラルそのまま使った方が移植性高かったよねという本末転倒なお話。

そもそも実装されたの N4 で当時はまだ gcc-4.1 だから二進数リテラルは使えなかったから(実装は gcc-4.3 以降)仕方ない話ではあるんだが。

そんで C23 ではようやっと標準となったんだから 少なくとも __BIT/__BITS は今すぐ捨てようってこと。