有没有用于以二进制格式打印的printf转换器?

588

我可以使用printf将数字以十六进制或八进制形式打印出来。是否有格式标签可以打印成二进制或其他任意进制?

我正在运行gcc。


printf("%d %x %o\n", 10, 10, 10); //prints "10 A 12\n"
printf("%b\n", 10); // prints "%b\n"

据我所知,使用printf是无法做到这一点的。当然,你可以编写一个辅助方法来实现这个目标,但这似乎不是你想要走的方向。 - Ian P
15
不作为ANSI标准C库的一部分--如果您正在编写可移植代码,最安全的方法是自己编写。 - tomlogic
在 C++ 中将整数类型转换为二进制字符串的一个语句标准和通用(适用于任何长度的整数类型)解决方案:https://dev59.com/dHVD5IYBdhLWcg3wBm5h#31660310 - luart
没有这种格式。但是您为什么需要它?实现二进制打印非常容易,而且很少有必要使用这种格式 - 正因为如此,所以没有被实现。 - i486
所以预期输出是1010,对吗? - Ciro Santilli OurBigBook.com
显示剩余2条评论
58个回答

5

用更少的代码和资源打印出任何类型的位

这种方法具有以下特点:

  • 可以使用变量和字面量。
  • 当不必要时,不会迭代所有位。
  • 只在完整的字节时调用printf(不会为所有位不必要地调用)。
  • 适用于任何类型。
  • 适用于小端和大端(使用GCC #defines进行检查)。
  • 可以在char不是一个字节(八个位)的硬件上使用。(感谢@supercat)
  • 使用typeof(),它不是C标准,但已被广泛定义。
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <limits.h>

#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
#define for_endian(size) for (int i = 0; i < size; ++i)
#elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
#define for_endian(size) for (int i = size - 1; i >= 0; --i)
#else
#error "Endianness not detected"
#endif

#define printb(value)                                   \
({                                                      \
        typeof(value) _v = value;                       \
        __printb((typeof(_v) *) &_v, sizeof(_v));       \
})

#define MSB_MASK 1 << (CHAR_BIT - 1)

void __printb(void *value, size_t size)
{
        unsigned char uc;
        unsigned char bits[CHAR_BIT + 1];

        bits[CHAR_BIT] = '\0';
        for_endian(size) {
                uc = ((unsigned char *) value)[i];
                memset(bits, '0', CHAR_BIT);
                for (int j = 0; uc && j < CHAR_BIT; ++j) {
                        if (uc & MSB_MASK)
                                bits[j] = '1';
                        uc <<= 1;
                }
                printf("%s ", bits);
        }
        printf("\n");
}

int main(void)
{
        uint8_t c1 = 0xff, c2 = 0x44;
        uint8_t c3 = c1 + c2;

        printb(c1);
        printb((char) 0xff);
        printb((short) 0xff);
        printb(0xff);
        printb(c2);
        printb(0x44);
        printb(0x4411ff01);
        printb((uint16_t) c3);
        printb('A');
        printf("\n");

        return 0;
}

输出

$ ./printb 
11111111 
11111111 
00000000 11111111 
00000000 00000000 00000000 11111111 
01000100 
00000000 00000000 00000000 01000100 
01000100 00010001 11111111 00000001 
00000000 01000011 
00000000 00000000 00000000 01000001 

我采用了另一种方法(bitprint.h),将所有字节(作为位字符串)填充到表格中,并根据输入/索引字节打印它们。值得一看。


1
我在使用硬件供应商的库时,实际上遇到了VLAs崩溃问题,这是在我最喜欢的嵌入式编译器上发生的。有些人会认为我应该只使用gcc或clang,但它们除了-O0之外没有其他设置,这将避免进行不安全的优化(例如假设如果编译器不需要适应p1用于访问某个上下文中的某个存储,并且编译器可以显示p1和p2将相等,则可以忽略p2用于访问该存储的可能性)。 - supercat

5

1
之前的回答中提到:“一些实现提供了itoa()函数,但大多数情况下不会有它”。 - Peter Mortensen

4
也许有人会发现这个解决方案很有用:
void print_binary(int number, int num_digits) {
    int digit;
    for(digit = num_digits - 1; digit >= 0; digit--) {
        printf("%c", number & (1 << digit) ? '1' : '0');
    }
}

4

使用标准库将任何整数类型转换为二进制字符串表示的通用语句:

#include <bitset>
MyIntegralType  num = 10;
print("%s\n",
    std::bitset<sizeof(num) * 8>(num).to_string().insert(0, "0b").c_str()
); // prints "0b1010\n"

或者只需: std::cout << std::bitset<sizeof(num) * 8>(num);

(注:该代码可以在 C++ 中将数字按二进制格式打印出来,无需解释。)

4
这是一个适用于 C++ 的惯用解决方案,但他要求的是 C 语言。 - danijar

3

根据@ideasman42在他的答案中的建议,这是一个宏,提供int8163264版本,重用INT8宏以避免重复。

/* --- PRINTF_BYTE_TO_BINARY macro's --- */
#define PRINTF_BINARY_SEPARATOR
#define PRINTF_BINARY_PATTERN_INT8 "%c%c%c%c%c%c%c%c"
#define PRINTF_BYTE_TO_BINARY_INT8(i)    \
    (((i) & 0x80ll) ? '1' : '0'), \
    (((i) & 0x40ll) ? '1' : '0'), \
    (((i) & 0x20ll) ? '1' : '0'), \
    (((i) & 0x10ll) ? '1' : '0'), \
    (((i) & 0x08ll) ? '1' : '0'), \
    (((i) & 0x04ll) ? '1' : '0'), \
    (((i) & 0x02ll) ? '1' : '0'), \
    (((i) & 0x01ll) ? '1' : '0')

#define PRINTF_BINARY_PATTERN_INT16 \
    PRINTF_BINARY_PATTERN_INT8               PRINTF_BINARY_SEPARATOR              PRINTF_BINARY_PATTERN_INT8
#define PRINTF_BYTE_TO_BINARY_INT16(i) \
    PRINTF_BYTE_TO_BINARY_INT8((i) >> 8),   PRINTF_BYTE_TO_BINARY_INT8(i)
#define PRINTF_BINARY_PATTERN_INT32 \
    PRINTF_BINARY_PATTERN_INT16              PRINTF_BINARY_SEPARATOR              PRINTF_BINARY_PATTERN_INT16
#define PRINTF_BYTE_TO_BINARY_INT32(i) \
    PRINTF_BYTE_TO_BINARY_INT16((i) >> 16), PRINTF_BYTE_TO_BINARY_INT16(i)
#define PRINTF_BINARY_PATTERN_INT64    \
    PRINTF_BINARY_PATTERN_INT32              PRINTF_BINARY_SEPARATOR              PRINTF_BINARY_PATTERN_INT32
#define PRINTF_BYTE_TO_BINARY_INT64(i) \
    PRINTF_BYTE_TO_BINARY_INT32((i) >> 32), PRINTF_BYTE_TO_BINARY_INT32(i)
/* --- end macros --- */

#include <stdio.h>
int main() {
    long long int flag = 1648646756487983144ll;
    printf("My Flag "
           PRINTF_BINARY_PATTERN_INT64 "\n",
           PRINTF_BYTE_TO_BINARY_INT64(flag));
    return 0;
}

这将输出:
My Flag 0001011011100001001010110111110101111000100100001111000000101000

为了提高可读性,你可以将:#define PRINTF_BINARY_SEPARATOR 改为 #define PRINTF_BINARY_SEPARATOR "," 或者 #define PRINTF_BINARY_SEPARATOR " "

这将输出:

My Flag 00010110,11100001,00101011,01111101,01111000,10010000,11110000,00101000

或者

My Flag 00010110 11100001 00101011 01111101 01111000 10010000 11110000 00101000

3
没有标准且可移植的方法。
一些实现提供itoa(),但大多数情况下不会提供,并且其接口有点糟糕。但是代码在链接后面,应该可以让您轻松地实现自己的格式化程序。

3
void print_ulong_bin(const unsigned long * const var, int bits) {
        int i;

        #if defined(__LP64__) || defined(_LP64)
                if( (bits > 64) || (bits <= 0) )
        #else
                if( (bits > 32) || (bits <= 0) )
        #endif
                return;

        for(i = 0; i < bits; i++) { 
                printf("%lu", (*var >> (bits - 1 - i)) & 0x01);
        }
}

应该可以工作 - 未经测试。


3
这是一个与 paniq 的解决方案略有不同的版本,它使用模板来允许打印32位和64位整数:
template<class T>
inline std::string format_binary(T x)
{
    char b[sizeof(T)*8+1] = {0};

    for (size_t z = 0; z < sizeof(T)*8; z++)
        b[sizeof(T)*8-1-z] = ((x>>z) & 0x1) ? '1' : '0';

    return std::string(b);
}

可以这样使用:

unsigned int value32 = 0x1e127ad;
printf( "  0x%x: %s\n", value32, format_binary(value32).c_str() );

unsigned long long value64 = 0x2e0b04ce0;
printf( "0x%llx: %s\n", value64, format_binary(value64).c_str() );

这是结果:

  0x1e127ad: 00000001111000010010011110101101
0x2e0b04ce0: 0000000000000000000000000000001011100000101100000100110011100000

1
这不是C语言,而是使用C++编写的糟糕的面向对象编程。 - TechnicTechnician

3

我只是想发布我的解决方案。它用于获取一个字节的零和一,但是可以多次调用此函数以用于更大的数据块。我将其用于128位或更大的结构体。您还可以修改它,使用size_t作为输入参数和指向要打印的数据的指针,以使其大小独立。但是按照目前的方式它对我来说已经很好用了。

void print_binary(unsigned char c)
{
 unsigned char i1 = (1 << (sizeof(c)*8-1));
 for(; i1; i1 >>= 1)
      printf("%d",(c&i1)!=0);
}

void get_binary(unsigned char c, unsigned char bin[])
{
 unsigned char i1 = (1 << (sizeof(c)*8-1)), i2=0;
 for(; i1; i1>>=1, i2++)
      bin[i2] = ((c&i1)!=0);
}

3
另一种以二进制形式打印的方法:先将整数转换为二进制
要以二进制形式打印6,请将其更改为110,然后打印"110"
绕过char buf[]问题。
printf()格式说明符、标志和字段,如"%08lu""%*lX"仍可轻松使用。
不仅限于二进制(基数2),此方法可扩展到其他基数,最高可达16位。
仅适用于较小的整数值。
#include <stdint.h>
#include <stdio.h>
#include <inttypes.h>

unsigned long char_to_bin10(char ch) {
  unsigned char uch = ch;
  unsigned long sum = 0;
  unsigned long power = 1;
  while (uch) {
    if (uch & 1) {
      sum += power;
      }
   power *= 10;
   uch /= 2;
  }
  return sum;
}

uint64_t uint16_to_bin16(uint16_t u) {
  uint64_t sum = 0;
  uint64_t power = 1;
  while (u) {
    if (u & 1) {
      sum += power;
      }
    power *= 16;
    u /= 2;
  }
  return sum;
}

void test(void) {
  printf("%lu\n", char_to_bin10(0xF1));
  // 11110001
  printf("%" PRIX64 "\n", uint16_to_bin16(0xF731));
  // 1111011100110001
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接