有没有用于以二进制格式打印的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个回答

384

虽然不太规范,但对我来说很有效:

#define BYTE_TO_BINARY_PATTERN "%c%c%c%c%c%c%c%c"
#define BYTE_TO_BINARY(byte)  \
  ((byte) & 0x80 ? '1' : '0'), \
  ((byte) & 0x40 ? '1' : '0'), \
  ((byte) & 0x20 ? '1' : '0'), \
  ((byte) & 0x10 ? '1' : '0'), \
  ((byte) & 0x08 ? '1' : '0'), \
  ((byte) & 0x04 ? '1' : '0'), \
  ((byte) & 0x02 ? '1' : '0'), \
  ((byte) & 0x01 ? '1' : '0') 

printf("Leading text "BYTE_TO_BINARY_PATTERN, BYTE_TO_BINARY(byte));

对于多字节类型
printf("m: "BYTE_TO_BINARY_PATTERN" "BYTE_TO_BINARY_PATTERN"\n",
  BYTE_TO_BINARY(m>>8), BYTE_TO_BINARY(m));

不幸的是,您需要所有额外的引号。这种方法具有宏的效率风险(不要将函数作为参数传递给BYTE_TO_BINARY),但避免了其他一些提议中的内存问题和多次调用strcat。


18
具有这种优势的还可以在 printf 中多次调用,而具有静态缓冲区的则不能。 - Patrick Schlüter
7
我已经擅自将 "%d" 改成了 "%c",因为这样会更快("%d" 需要进行数字到字符的转换,而 "%c" 只是输出参数)。 - user719662
3
已发布支持16、32、64位整数的扩展宏的版本:https://dev59.com/dHVD5IYBdhLWcg3wBm5h#25108449。 - ideasman42
3
请注意,这种方法不适合使用栈。假设系统中的“int”为32位,则打印单个32位值将需要32个4字节值的空间;总共128个字节。这取决于栈大小,可能会或可能不会成为问题。 - user694733
3
在该宏中加上字节周围的括号非常重要,否则在发送操作BYTE_TO_BINARY(a | b)时可能会遇到问题 -> a | b & 0x01 != (a | b) & 0x01 - Ivan Hoffmann
显示剩余5条评论

237

打印任何数据类型的二进制

// Assumes little endian
void printBits(size_t const size, void const * const ptr)
{
    unsigned char *b = (unsigned char*) ptr;
    unsigned char byte;
    int i, j;
    
    for (i = size-1; i >= 0; i--) {
        for (j = 7; j >= 0; j--) {
            byte = (b[i] >> j) & 1;
            printf("%u", byte);
        }
    }
    puts("");
}

测试:

int main(int argv, char* argc[])
{
    int i = 23;
    uint ui = UINT_MAX;
    float f = 23.45f;
    printBits(sizeof(i), &i);
    printBits(sizeof(ui), &ui);
    printBits(sizeof(f), &f);
    return 0;
}

12
建议使用 size_t i; for (i=size; i-- > 0; ),避免 size_tint 不匹配的问题。 - chux - Reinstate Monica
2
对于指针ptr中的每个字节(外循环); 然后对当前字节中的每个位(内循环), 通过当前位掩码字节(1 << j),将字节进行掩码处理。然后右移,得到一个包含0(0000 0000b)或1 (0000 0001b)的字节。使用格式为 %u的printf打印结果字节。希望有所帮助。 - nielsbot
2
@ZX9 注意,建议的代码使用了size_t>而不是您评论中的>=来确定何时终止循环。 - chux - Reinstate Monica
4
您发表的原始评论仍然很有用,因为编程人员需要小心考虑在无符号类型中使用 >>= 的边缘情况。在无符号类型中,0 是一个边缘情况,而且经常出现,这与带符号数学不同,后者使用不太常见的 INT_MAX/INT_MIN - chux - Reinstate Monica
2
变量“byte”应该是“bit”吧?而“b”实际上是字节数组。 - Jetski S-type
显示剩余4条评论

167

这里有一个快速技巧,可以演示如何实现您想要的功能。

#include <stdio.h>      /* printf */
#include <string.h>     /* strcat */
#include <stdlib.h>     /* strtol */

const char *byte_to_binary
(
    int x
)
{
    static char b[9];
    b[0] = '\0';

    int z;
    for (z = 128; z > 0; z >>= 1)
    {
        strcat(b, ((x & z) == z) ? "1" : "0");
    }

    return b;
}

int main
(
    void
)
{
    {
        /* binary string to int */

        char *tmp;
        char *b = "0101";

        printf("%d\n", strtol(b, &tmp, 2));
    }

    {
        /* byte to binary string */

        printf("%s\n", byte_to_binary(5));
    }
    
    return 0;
}

3
这肯定比为printf自定义编写escape overload函数更“奇怪”。对于新接手这份代码的开发人员来说,它也很容易理解。 - Furious Coder
49
一些更改:strcat方法在每次循环添加单个字符到字符串上的效率较低。 相反,应该添加一个char *p = b;,并用*p++ = (x & z) ? '1' : '0'替换内部循环。 z 应该从128(2^7)开始,而不是256(2^8)。 考虑更新以接受指向缓冲区的指针(以实现线程安全),类似于inet_ntoa() - tomlogic
3
@EvilTeach: 你把三目运算符作为 strcat() 的参数使用了!我同意 strcat 可能比对解引用指针后进行后缀递增更容易理解,但即使是初学者也需要知道如何正确地使用标准库。也许使用索引数组进行赋值会是一个不错的示范(而且实际上可以工作,因为每次调用函数时 b 不会被重置为全零)。 - tomlogic
3
随机数:二进制缓冲字符是静态的,并在赋值时清零。这将只在第一次运行时清除它,之后它不会清除,而是使用上一次的值。 - markwatson
9
此外,必须记录下调用函数后以前的结果将会失效,因此调用者不应该像这样使用它:printf("%s + %s = %s", byte_to_binary(3), byte_to_binary(4), byte_to_binary(3+4)) - Paŭlo Ebermann
显示剩余8条评论

92

在glibc中通常没有二进制转换说明符。

可以向glibc的printf()函数族中添加自定义转换类型。有关详细信息,请参见 register_printf_function。如果您发现自定义 %b 转换可简化应用程序代码并使其更易于使用,那么您可以添加它供自己使用。

以下是在glibc中实现自定义printf格式的 示例


6
警告:'register_printf_function' 已经被弃用[-Wdeprecated-declarations]。虽然有一个新的函数可以完成相同的操作:register_printf_specifier()。新使用方法的示例可以在此处找到:https://codereview.stackexchange.com/q/219994/200418。 - alx - recommends codidact

69

您可以使用一个小表格来提高速度1。类似的技术在嵌入式领域也很有用,例如,倒转一个字节:

const char *bit_rep[16] = {
    [ 0] = "0000", [ 1] = "0001", [ 2] = "0010", [ 3] = "0011",
    [ 4] = "0100", [ 5] = "0101", [ 6] = "0110", [ 7] = "0111",
    [ 8] = "1000", [ 9] = "1001", [10] = "1010", [11] = "1011",
    [12] = "1100", [13] = "1101", [14] = "1110", [15] = "1111",
};

void print_byte(uint8_t byte)
{
    printf("%s%s", bit_rep[byte >> 4], bit_rep[byte & 0x0F]);
}

1 我主要是指嵌入式应用程序,其中优化器不太激进,速度差异是可见的。


它能工作!但是用于定义bit_rep的语法是什么? - 0xB00B
这段代码看起来很不错。但是你如何更新这段代码以处理uint16_t、uint32_t和uint64_t? - RobK
1
@Robk,4、8和16个%s以及相同数量的bit_rep[word >> 4K & 0xF..F]参数应该就可以了。虽然我认为对于一个64位数字来说,输出16个字符串可能不会比循环64次并输出0/1更快。 - Shahbaz

46

打印最低位并将其从右侧移出。重复此操作直到整数变为零,将以反向顺序打印二进制表示,但不带前导零。使用递归,可以很容易地纠正顺序。

#include <stdio.h>

void print_binary(unsigned int number)
{
    if (number >> 1) {
        print_binary(number >> 1);
    }
    putc((number & 1) ? '1' : '0', stdout);
}

对我来说,这是解决问题最干净的方法之一。如果你喜欢使用0b前缀和一个换行符,请建议包装该函数。

在线演示


10
你也应该使用无符号整数,因为当给定的数字为负数时,函数会进入无限递归调用。 - Puffy
3
жӣҙй«ҳж•Ҳзҡ„ж–№жі•пјҢеӣ дёәеңЁASCIIз ҒдёӯпјҢ'0'+1='1'пјҡputc('0'+(number&1), stdout); - Roger Dueck
我已经修改了这个函数,使其能够处理等于或小于0的int值。 - isrnick
将值0x80传递给您的函数,结果与预期不符。 - choppe

39

截至2022年2月3日,GNU C库已更新至2.35版本。因此,%b现在支持以二进制格式输出。

printf系列函数现在支持使用%b格式以二进制形式输出整数,该格式已在ISO C2X草案中规定,并且还推荐使用该格式的%B变体。


这是我本周听到的最好的消息!刚刚检查了我的Ubuntu 22.04系统;ldd --version报告:ldd (Ubuntu GLIBC 2.35-0ubuntu3.1) 2.35!问题在于**man 3 printf%b%B完全保持沉默** :( - user5395338

32

基于 @William Whyte 的回答,这是一个宏,提供了int8163264 版本,重用 INT8 宏来避免重复。

/* --- PRINTF_BYTE_TO_BINARY macro's --- */
#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_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_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_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

为了易读性,您可能想添加一个分隔符,例如:

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

这很棒。有特别的原因要从最低有效位开始打印比特吗? - gaganso
2
你会如何建议添加逗号? - nmz787
可以添加一个分组版本的 PRINTF_BYTE_TO_BINARY_INT# 宏定义,以供选择使用。 - ideasman42

19

以下是一个函数的版本,它不会出现可重入性问题或参数大小/类型的限制:

#define FMT_BUF_SIZE (CHAR_BIT*sizeof(uintmax_t)+1)

char *binary_fmt(uintmax_t x, char buf[static FMT_BUF_SIZE])
{
    char *s = buf + FMT_BUF_SIZE;
    *--s = 0;
    if (!x) *--s = '0';
    for (; x; x /= 2) *--s = '0' + x%2;
    return s;
}

请注意,如果您将2替换为所需的进制,此代码将适用于2到10之间的任何基数。使用方法为:

char tmp[FMT_BUF_SIZE];
printf("%s\n", binary_fmt(x, tmp));

x 是任何整数表达式时。


9
是的,你可以那样做。但这是很糟糕的设计。即使你没有线程或可重入性,调用者也必须意识到静态缓冲区正在被重用,并且像char *a = binary_fmt(x), *b = binary_fmt(y);这样的语句将无法按预期工作。强制调用者传递缓冲区会使存储要求显式化; 当然,如果确实需要,调用者可以自由地使用静态缓冲区,那么重用同一缓冲区就变得显式化了。此外,请注意,在现代 PIC ABIs 上,与栈上的缓冲区相比,静态缓冲区通常需要更多的代码来访问。 - R.. GitHub STOP HELPING ICE
9
这仍然是一个糟糕的设计。在这些情况下,它需要进行额外的复制步骤,而且即使在不需要复制的情况下,它并不比要求调用者提供缓冲区更加经济。使用静态存储只是一个糟糕的惯用语。 - R.. GitHub STOP HELPING ICE
4
如果每个调用者都必须污染预处理器或变量符号表的命名空间,使用一个不必要的额外名称来正确确定必须分配的存储空间大小,并强制每个调用者知道这个值并分配必要的存储空间,那么这是一种糟糕的设计。当更简单的函数局部存储解决方案对于大多数意图和目的已经足够,并且当一个简单的strdup()调用可以涵盖99%的其余用途时,这种设计就更加不好了。 - Greg A. Woods
6
在这方面,我们的意见不同。我无法看出如何添加一个不引人注目的预处理符号会对限制使用情况、使接口容易出错、为程序的整个持续期保留临时值的永久存储以及在大多数现代平台上生成更糟糕的代码等方面造成的有害程度。 - R.. GitHub STOP HELPING ICE
7
我不主张没有理由(即测量数据)的微观优化。但是,当性能作为一个基本上优越的设计的额外奖励时,我认为即使它只是微小的改进,也值得一提。 - R.. GitHub STOP HELPING ICE
显示剩余11条评论

16

快速简单的解决方案:

void printbits(my_integer_type x)
{
    for(int i=sizeof(x)<<3; i; i--)
        putchar('0'+((x>>(i-1))&1));
}
任何类型大小都适用,包括有符号和无符号整数。"&1" 是必需的,以处理有符号整数,因为移位可能会进行符号扩展。
有很多方法可以做到这一点。下面是一个超级简单的方法,用于打印有符号或无符号 32 位类型的 32 位或 n 位(如果有),不打印负号,只打印实际位,并且不换行。请注意,在移位之前会将 i 减小:
#define printbits_n(x,n) for (int i=n;i;i--,putchar('0'|(x>>i)&1))
#define printbits_32(x) printbits_n(x,32)

那么将位存储或稍后打印的字符串返回如何?你可以分配内存并返回它,用户必须释放它,否则你可以返回一个静态字符串,但如果再次调用它,或者被另一个线程破坏了。这里展示了两种方法:

char *int_to_bitstring_alloc(int x, int count)
{
    count = count<1 ? sizeof(x)*8 : count;
    char *pstr = malloc(count+1);
    for(int i = 0; i<count; i++)
        pstr[i] = '0' | ((x>>(count-1-i))&1);
    pstr[count]=0;
    return pstr;
}

#define BITSIZEOF(x)    (sizeof(x)*8)

char *int_to_bitstring_static(int x, int count)
{
    static char bitbuf[BITSIZEOF(x)+1];
    count = (count<1 || count>BITSIZEOF(x)) ? BITSIZEOF(x) : count;
    for(int i = 0; i<count; i++)
        bitbuf[i] = '0' | ((x>>(count-1-i))&1);
    bitbuf[count]=0;
    return bitbuf;
}

与以下人员通话:

// memory allocated string returned which needs to be freed
char *pstr = int_to_bitstring_alloc(0x97e50ae6, 17);
printf("bits = 0b%s\n", pstr);
free(pstr);

// no free needed but you need to copy the string to save it somewhere else
char *pstr2 = int_to_bitstring_static(0x97e50ae6, 17);
printf("bits = 0b%s\n", pstr2);

我正在测试这个程序,看起来两种 *int_to_bitstring_ 方法都没有正确计算结果,或者是我漏掉了什么?printbits 运行正常。此外,对于大于32的十进制数,静态和分配方法的结果开始有所不同。我在C语言和位运算方面缺乏经验。 - edvard_munch

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