C23で追加されたビット操作関数<stdbit.h>を使ってみる

n-ozawan

皆さん、こんにちは。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進数で出力するための関数を用意するのが面倒だったのを思い出します。

ではまた。

Recommendおすすめブログ