如何在 C 中打印 1 位(不仅是最低有效位)?

How do I print 1 bit in C (not only the least significant bit)?

我正在开发一个发送 TCP header 的程序(模拟 3 次握手)。我有一个变量,这个变量包含数据偏移量(3 位)、保留(4 位)和 9 个标志(9 位)。我正在使用按位运算来设置位。问题是我怎样才能打印出每一位?

  1. 假设我存储从 2 到 4(从左到右)开始的数据偏移量:例如。 0111000000000000

如何打印这 3 位?变量:

u_int16_t reserved_ofs_flags;

我发现这个问题很相似,但答案只适用于最不重要的问题:How do I print one bit?

您可以使用 bitwise operations 来获取您想要的位。

例如:

unsigned int a = 22;           // 10110 in binary
printf("%d\n", a & 1);         // get the bit in the 1's place (0)
printf("%d\n", (a >> 1) & 1);  // get the bit in the 2's place (1)
printf("%d\n", (a >> 2) & 1);  // get the bit in the 4's place (1)
printf("%d\n", (a >> 3) & 1);  // get the bit in the 8's place (0)
printf("%d\n", (a >> 4) & 1);  // get the bit in the 16's place (1)

// 3 in decimal is 11 in binary
printf("%d\n", a & 3);         // get the bits in the 1's and the 2's places
                               // (2 in decimal, 10 in binary)
printf("%d\n", (a >> 1) & 3);  // get the bits in the 2's and the 4's places
                               // (3 in decimal, 11 in binary)

要从从位置 P 开始的 unsigned 值中提取 N 位,从 0 开始计算最低有效位,您可以使用此表达式:

unsigned x = (value >> P) & ((1U << (N - 1) << 1) - 1);

备注:

  • 左到右对位进行编号是一个令人困惑的约定。在软件中,首选的编号方法是从最低有效位(编号 0)到最高有效位(在您的示例中为编号 15)。
  • 如果 N 是编译时常量,则表达式 ((1U << (N - 1) << 1) - 1) 在编译时求值。
  • 表达式假定 N 至少是 1 并且最多是 unsigned 类型中的位数。
  • 如果 Nunsigned 类型中的位数,则更简单的表达式 ((1U << N) - 1) 具有未定义的行为。
  • 对于你的例子 P12N3 所以你可以写: unsigned x = (value >> 12) & 7;

如果我理解你的问题,并且你希望能够提取从位置 [=] 开始的 N 位数(从 1sizeof(type) * CHAR_BIT)的值22=](从 0sizeof(type) * CHAR_BIT - 1)然后你可以提取该位子集:

/** extract N bits from value starting at position P, 
 *  counting from 0 for the least significant bit
 */
unsigned nbitsatp (unsigned value, unsigned N, unsigned P)
{
  /* mask is N 1's bits */
  unsigned mask = ~0u >> ((sizeof mask * CHAR_BIT) - N); 
  
  return (value >> (P - N + 1)) & mask;
}

(注意: for 4-byte unsigned, N ranges from 1 to 32 while P 是 zero-based,范围从 031)

上面,mask以所有位1~0u)开始,然后移出总位数减去N(留下N 1的位)。然后将 value 移动 P - N + 1,这样您 AND 位置 P 所需的位数与相应的 1 位数。由于两者都被移位,因此值从最低有效位开始,结果是位置 P.

N 位的值

这避免了对您想要的每个位范围的数字和各个位位置进行硬编码。

在您的示例中,您想要从 0111000000000000 (28672) 中提取前 3 位,这将是位置 P == 15 的 3 位 N == 3,结果正在 011 (3).

一个简短的例子

下面的示例使用 unsigned 作为类型,只要它在您的硬件上至少为 2 个字节,就足以满足 uint16_t 类型。

#include <stdio.h>
#include <limits.h>

/** extract N bits from value starting at position P, 
 *  counting from 0 for the least significant bit
 */
unsigned nbitsatp (unsigned value, unsigned N, unsigned P)
{
  /* mask is N 1's bits */
  unsigned mask = ~0u >> ((sizeof mask * CHAR_BIT) - N); 
  
  return (value >> (P - N + 1)) & mask;
}

int main (void) {

  unsigned v, n, p;
  
  fputs ("enter v, n, p : ", stdout);    /* prompt for v, n, p */
  
  /* read/validate positive int value */
  if (scanf ("%u%u%u", &v, &n, &p) != 3) {
    fputs ("error: invalid unsigned integer input.\n", stderr);
    return 1;
  }
  
  /* output result */
  printf ("\nvalue of %u bits at pos %u in %u is : %u\n", 
          n, p, v, nbitsatp (v, n, p));
}

例子Use/Output

您想要从 28672 的位置 15 开始的 3 位的具体示例:

$ ./bin/nbitsatp
enter v, n, p : 28672 3 15

value of 3 bits at pos 15 in 28672 is : 3

或者我们取第15位的前4位,0111 (7):

$  ./bin/nbitsatp
enter v, n, p : 28672 4 15

value of 4 bits at pos 15 in 28672 is : 7

或者从位置 15 开始的前 5 位:

$ ./bin/nbitsatp
enter v, n, p : 28672 5 15

value of 5 bits at pos 15 in 28672 is : 14

或者在您的示例中全为零的 9 位标志(9 位,位置 8)的值如何:

$ ./bin/nbitsatp
enter v, n, p : 28672 9 8

value of 9 bits at pos 8 in 28672 is : 0

使用 Pre-Defined 宏检索所需位

使用 nbitsatp() 函数检索您感兴趣的位的一种简便方法是 #define 为您要获取的每组位创建一个宏。例如,要获取数据偏移量的 3 位、保留的 4 位和 9 位标志集,您可以定义三个宏来设置位数和位置,从而允许您简单地传递 TCP Header 值作为参数,例如

/* macros for 3-bit offset, 4-bit reserved, 9-bit flags */
#define HDR_OFFSET(TCPHDRVAL) nbitsatp ((TCPHDRVAL), 3, 15)
#define HDR_RESERVED(TCPHDRVAL) nbitsatp ((TCPHDRVAL), 4, 12)
#define HDR_FLAGS(TCPHDRVAL) nbitsatp ((TCPHDRVAL), 9, 8)

要获得您想要的位,只需调用将 TCP header 值作为参数传递的宏,例如

int main (void) {

  unsigned v;
  
  fputs ("enter TCP hdr value : ", stdout);   /* prompt TCP HDR VAL */
  
  /* read/validate TCP header value */
  if (scanf ("%u", &v) != 1) {
    fputs ("error: invalid unsigned integer input.\n", stderr);
    return 1;
  }
  
  /* output result */
  printf ("\n   data offset bits : %u\n"
          "   reserved bits    : %u\n"
          "   flag bits        : %u\n", 
          HDR_OFFSET (v), HDR_RESERVED (v), HDR_FLAGS (v));
}

输出

$ /bin/nbitsatp_macro
enter TCP hdr value : 28672

   data offset bits : 3
   reserved bits    : 8
   flag bits        : 0

如果需要,您可以输出 3 (011)、8 (1000) 的填充二进制表示,然后是标志 (000000000) 以一种微不足道的方式。参见 binprnpad() function in this answer