C23で追加されたビット操作関数<stdbit.h>を使ってみる
皆さん、こんにちは。LP開発グループのn-ozawanです。
明けましておめでとうございます。本年もよろしくお願いいたします。
本題です。
2024年に発行されたC言語の標準規格であるC23(ISO/IEC 9899:2024)に、高度なビット操作を可能とする<stdbit.h>が追加されました。ちょっと気になったので、今回は<stdbit.h>を触ってみます。
目次
ビット操作関数<stdbit.h>
概要
<stdbit.h>は、2024年に発行されたC言語の標準規格であるC23に追加された、高度なビット操作を可能とする関数が集められたヘッダーです。C言語は組み込みなどでも現役で使われており、限られたリソース環境でプログラムを動かすために、ビット単位で情報を処理することがよくあります。
今回はそんな<stdbit.h>に収録されている関数およびマクロで何ができるのかを紹介します。
ビットの1もしくは0の個数を数える
stdc_count_ones()とstdc_count_zeros()は受け取った値のビット列から、1もしくは0の数を数えます。
uint32_t x = 0x00F0u;
printf("x = 0x%08X (%032b)\n", x, x); // 0x000000F0 (00000000000000000000000011110000)
printf("stdc_count_ones(x) = %d\n", stdc_count_ones(x)); // 4
printf("stdc_count_zeros(x) = %d\n", stdc_count_zeros(x)); // 28先頭もしくは末尾の1もしくは0の個数を数える
stdc_leading_ones()とstdc_leading_zeros()は受け取った値のビット列の先頭から、1もしくは0が続く個数を数えます。stdc_trailing_ones()とstdc_trailing_zeros()はその逆で、受け取った値のビット列の末尾から、1もしくは0が続く個数を数えます。
uint32_t x1 = 0xF00000FFu;
printf("x = 0x%08X (%032b)\n", x1, x1); // 0xF00000FF (11110000000000000000000011111111)
printf("stdc_leading_ones(x1) = %d\n", stdc_leading_ones(x1)); // 4
printf("stdc_trailing_ones(x1) = %d\n", stdc_trailing_ones(x1)); // 8
printf("stdc_leading_zeros(x1) = %d\n", stdc_leading_zeros(x1)); // 0
printf("stdc_trailing_zeros(x1) = %d\n", stdc_trailing_zeros(x1)); // 0
uint32_t x2 = 0x00F0u;
printf("x = 0x%08X (%032b)\n", x2, x2); // 0x000000F0 (00000000000000000000000011110000)
printf("stdc_leading_ones(x2) = %d\n", stdc_leading_ones(x2)); // 0
printf("stdc_trailing_ones(x2) = %d\n", stdc_trailing_ones(x2)); // 0
printf("stdc_leading_zeros(x2) = %d\n", stdc_leading_zeros(x2)); // 24
printf("stdc_trailing_zeros(x2) = %d\n", stdc_trailing_zeros(x2)); // 4最上位もしくは最下位から数えて、最初に1もしくは0が現れる位置を探す
stdc_first_leading_one()とstdc_first_leading_zero()は受け取った値のビット列の先頭から、1もしくは0が現れる位置を探します。stdc_first_trailing_one()とstdc_first_trailing_zero()はその逆で、受け取った値のビット列の末尾から、1もしくは0が現れる位置を探します。
uint32_t x1 = 0x00400000u;
printf("x = 0x%08X (%032b)\n", x1, x1); // 0x00400000 (00000000010000000000000000000000)
printf("stdc_first_leading_one(x1) = %d\n", stdc_first_leading_one(x1)); // 10
printf("stdc_first_trailing_one(x1) = %d\n", stdc_first_trailing_one(x1)); // 23
printf("stdc_first_leading_zero(x1) = %d\n", stdc_first_leading_zero(x1)); // 1
printf("stdc_first_trailing_zero(x1) = %d\n", stdc_first_trailing_zero(x1)); // 1
uint32_t x2 = 0xFFFFEFFFu;
printf("x = 0x%08X (%032b)\n", x2, x2); // 0xFFFFEFFF (11111111111111111110111111111111)
printf("stdc_first_leading_one(x2) = %d\n", stdc_first_leading_one(x2)); // 1
printf("stdc_first_trailing_one(x2) = %d\n", stdc_first_trailing_one(x2)); // 1
printf("stdc_first_leading_zero(x2) = %d\n", stdc_first_leading_zero(x2)); // 20
printf("stdc_first_trailing_zero(x2) = %d\n", stdc_first_trailing_zero(x2)); // 13値が2のべき乗であるかを確認する
has_single_bit()は受け取った値が2のべき乗であるかを判定します。「2のべき乗」と言うことは、その値のビット列に1が1つしかない、と言うことです。
uint8_t samples[] = {0u, 1u, 2u, 3u, 4u, 7u, 8u, 16u};
for (size_t i = 0; i < sizeof(samples)/sizeof(samples[0]); ++i) {
uint8_t v = samples[i];
printf("v=0x%02X(%08b): has_single_bit=%d\n", v, v, stdc_has_single_bit(v));
}
// v=0x00(00000000): has_single_bit=0
// v=0x01(00000001): has_single_bit=1
// v=0x02(00000010): has_single_bit=1
// v=0x03(00000011): has_single_bit=0
// v=0x04(00000100): has_single_bit=1
// v=0x07(00000111): has_single_bit=0
// v=0x08(00001000): has_single_bit=1
// v=0x10(00010000): has_single_bit=1最大もしくは最小の2のべき乗を計算する
stdc_bit_floor()は受け取った値以下となる最大の2のべき乗を返却します。stdc_bit_ceil()は受け取った値以上となる最小の2のべき乗を返却します。「以上」と「以下」ですので、2のべき乗になる値を渡すと、その値がそのまま返却されます。
uint8_t samples[] = {0u, 1u, 2u, 3u, 4u, 7u, 8u, 16u};
for (size_t i = 0; i < sizeof(samples)/sizeof(samples[0]); ++i) {
uint8_t v = samples[i];
printf("v=0x%02X(%08b): bit_floor=0x%02X, bit_ceil=0x%02X\n",
v, v, stdc_bit_floor(v), stdc_bit_ceil(v));
}
// v=0x00(00000000): bit_floor=0x00, bit_ceil=0x01
// v=0x01(00000001): bit_floor=0x01, bit_ceil=0x01
// v=0x02(00000010): bit_floor=0x02, bit_ceil=0x02
// v=0x03(00000011): bit_floor=0x02, bit_ceil=0x04
// v=0x04(00000100): bit_floor=0x04, bit_ceil=0x04
// v=0x07(00000111): bit_floor=0x04, bit_ceil=0x08
// v=0x08(00001000): bit_floor=0x08, bit_ceil=0x08
// v=0x10(00010000): bit_floor=0x10, bit_ceil=0x10
必要なビット数を調べる
stdc_bit_width()は受け取った値を表現するのに必要なビット数を返却します。
uint8_t samples[] = {0u, 1u, 2u, 3u, 4u, 7u, 8u, 16u, 31u, 32u, 63u, 64u};
for (size_t i = 0; i < sizeof(samples)/sizeof(samples[0]); ++i) {
uint8_t v = samples[i];
printf("v=0x%02X(%08b): bit_width=%d\n", v, v, stdc_bit_width(v));
}
// v=0x00(00000000): bit_width=0
// v=0x01(00000001): bit_width=1
// v=0x02(00000010): bit_width=2
// v=0x03(00000011): bit_width=2
// v=0x04(00000100): bit_width=3
// v=0x07(00000111): bit_width=3
// v=0x08(00001000): bit_width=4
// v=0x10(00010000): bit_width=5
// v=0x1F(00011111): bit_width=5
// v=0x20(00100000): bit_width=6
// v=0x3F(00111111): bit_width=6
// v=0x40(01000000): bit_width=7エンディアンを調べる
エンディアンを判別するためのマクロ__STDC_ENDIAN_LITTLE__、__STDC_ENDIAN_BIG__、__STDC_ENDIAN_NATIVE__が追加されています。
#if __STDC_ENDIAN_NATIVE__ == __STDC_ENDIAN_LITTLE__
// リトルエンディアン
#elif __STDC_ENDIAN_NATIVE__ == __STDC_ENDIAN_BIG__
// ビッグエンディアン
#endifおわりに
使ってみての感想ですが、使い道は限定的な気がします。それよりもprintf()の%bで2進数表示できるようになったことの方がうれしいですね。デバッグ時にビットの状況を知りたくてログ出力するために、2進数で出力するための関数を用意するのが面倒だったのを思い出します。
ではまた。
