もう BIT(3) 使うの止めませんか
そもそも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
は今すぐ捨てようってこと。