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

15

是否有一个 printf 转换器以二进制格式打印?

printf() 系列只能使用标准的转换符直接打印8进制、10进制和16进制的整数。我建议创建一个函数,根据代码的特定需求将数字转换为字符串。

[编辑 2022] 下一个C语言版本将实现 "%b",支持二进制常量如0b10101010,以及printf()函数族的%b转换说明符。

C2x 的特性之一是支持二进制常量以及 %b 转换说明符。


以任意进制 [2-36] 打印

迄今为止,所有其他答案都具有这些限制中的至少一项。

  1. 使用静态内存返回缓冲区,这限制了该函数可以作为参数传递给printf()的次数。

  2. 分配需要调用代码释放指针的内存。

  3. 要求调用代码显式提供适当的缓冲区。

  4. 直接调用 printf()。这需要为 fprintf(), sprintf(), vsprintf() 等新的函数编写。

  5. 使用缩小的整数范围。

以下内容没有以上限制。它需要 C99 或更高版本,并使用"%s"。它使用一个复合字面量来提供缓冲区空间。在多次调用 printf() 的情况下也没有问题。

#include <assert.h>
#include <limits.h>
#define TO_BASE_N (sizeof(unsigned)*CHAR_BIT + 1)

//                               v--compound literal--v
#define TO_BASE(x, b) my_to_base((char [TO_BASE_N]){""}, (x), (b))

// Tailor the details of the conversion function as needed
// This one does not display unneeded leading zeros
// Use return value, not `buf`
char *my_to_base(char buf[TO_BASE_N], unsigned i, int base) {
  assert(base >= 2 && base <= 36);
  char *s = &buf[TO_BASE_N - 1];
  *s = '\0';
  do {
    s--;
    *s = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"[i % base];
    i /= base;
  } while (i);

  // Could employ memmove here to move the used buffer to the beginning
  // size_t len = &buf[TO_BASE_N] - s;
  // memmove(buf, s, len);

  return s;
}

#include <stdio.h>
int main(void) {
  int ip1 = 0x01020304;
  int ip2 = 0x05060708;
  printf("%s %s\n", TO_BASE(ip1, 16), TO_BASE(ip2, 16));
  printf("%s %s\n", TO_BASE(ip1, 2), TO_BASE(ip2, 2));
  puts(TO_BASE(ip1, 8));
  puts(TO_BASE(ip1, 36));
  return 0;
}

输出

1020304 5060708
1000000100000001100000100 101000001100000011100001000
100401404
A2F44

2
这非常有用。你知道如何在C++中使用它吗?当我编译时,它会生成一个错误:"严重程度代码描述项目文件行抑制状态 错误C4576:括号内类型后跟初始化列表是一种非标准的显式类型转换语法 hello C:\my_projects\hello\hello\main.cpp 39"。 - Just a learner
1
@Justalearner 这会生成一个 C++,因为它使用了 C 的特性 _compound literal_,而这不是 C++ 的一部分。也许你可以发布你的 C++ 实现,尝试做同样的事情 - 即使不完整,我相信你会得到帮助 - 只要你首先展示你的尝试。 - chux - Reinstate Monica
1
这正是我所期望的质量!感谢分享。 - gberth
@Justalearner “这会生成 C++,因为它使用了 C 特性” 应该是 “这会生成 C++ _错误_,因为它使用了 C 特性”。 - chux - Reinstate Monica

13
const char* byte_to_binary(int x)
{
    static char b[sizeof(int)*8+1] = {0};
    int y;
    long long z;

    for (z = 1LL<<sizeof(int)*8-1, y = 0; z > 0; z >>= 1, y++) {
        b[y] = (((x & z) == z) ? '1' : '0');
    }
    b[y] = 0;

    return b;
}

1
不错的解决方案。不过我会做一些改变,比如倒序遍历字符串,这样可以正确处理任何大小的输入。 - Kobor42
所有的 8 应该被替换为 CHAR_BIT - alk
我喜欢它不使用任何字符串库,因此可以轻松地在嵌入式环境中使用。 - jvh
使用静态变量对于这个函数来说真的很糟糕。想象一下 printf(byte_to_binary(1), byte_to_binary(5)),其中一个调用会覆盖另一个调用的字符串。 - Neuron

12

之前发布的回答都不是我所寻找的,因此我自己写了一个。使用printf非常简单,只需要使用%B即可!

/*
 * File:   main.c
 * Author: Techplex.Engineer
 *
 * Created on February 14, 2012, 9:16 PM
 */

#include <stdio.h>
#include <stdlib.h>
#include <printf.h>
#include <math.h>
#include <string.h>

static int printf_arginfo_M(const struct printf_info *info, size_t n, int *argtypes)
{
    /* "%M" always takes one argument, a pointer to uint8_t[6]. */
    if (n > 0) {
        argtypes[0] = PA_POINTER;
    }
    return 1;
}

static int printf_output_M(FILE *stream, const struct printf_info *info, const void *const *args)
{
    int value = 0;
    int len;

    value = *(int **) (args[0]);

    // Beginning of my code ------------------------------------------------------------
    char buffer [50] = "";  // Is this bad?
    char buffer2 [50] = "";  // Is this bad?
    int bits = info->width;
    if (bits <= 0)
        bits = 8;  // Default to 8 bits

    int mask = pow(2, bits - 1);
    while (mask > 0) {
        sprintf(buffer, "%s", ((value & mask) > 0 ? "1" : "0"));
        strcat(buffer2, buffer);
        mask >>= 1;
    }
    strcat(buffer2, "\n");
    // End of my code --------------------------------------------------------------
    len = fprintf(stream, "%s", buffer2);
    return len;
}

int main(int argc, char** argv)
{
    register_printf_specifier('B', printf_output_M, printf_arginfo_M);

    printf("%4B\n", 65);

    return EXIT_SUCCESS;
}

1
这将会在超过50位时发生溢出吗? - Janus Troelsen
好的,没错...我被告知需要使用malloc,你用过吗? - TechplexEngineer
当然可以。非常简单:char* buffer = (char*) malloc(sizeof(char) * 50); - Janus Troelsen
@JanusTroelsen,或者更加简洁、小巧、易于维护的方式是:char *buffer = malloc(sizeof(*buffer) * 50); - Shahbaz
1
为什么在这方面,“%B”会与“%b”有所不同呢?以前的答案说过,诸如“C标准库中没有格式化函数可以输出二进制数”和“一些运行时支持‘%b’,虽然这不是标准”。 - Peter Mortensen

10

这段代码应该能够处理你的需求,最多可以达到64位。 我创建了两个函数:pBinpBinFill。它们都做同样的事情,但是pBinFill会使用其最后一个参数提供的填充字符来填充前导空格。 测试函数生成一些测试数据,然后使用pBinFill函数打印出来。

#define kDisplayWidth 64

char* pBin(long int x,char *so)
{
  char s[kDisplayWidth+1];
  int i = kDisplayWidth;
  s[i--] = 0x00;  // terminate string
  do {  // fill in array from right to left
    s[i--] = (x & 1) ? '1' : '0';  // determine bit
    x >>= 1;  // shift right 1 bit
  } while (x > 0);
  i++;  // point to last valid character
  sprintf(so, "%s", s+i);  // stick it in the temp string string
  return so;
}

char* pBinFill(long int x, char *so, char fillChar)
{
  // fill in array from right to left
  char s[kDisplayWidth+1];
  int i = kDisplayWidth;
  s[i--] = 0x00;  // terminate string
  do {  // fill in array from right to left
    s[i--] = (x & 1) ? '1' : '0';
    x >>= 1;  // shift right 1 bit
  } while (x > 0);
  while (i >= 0) s[i--] = fillChar;  // fill with fillChar 
  sprintf(so, "%s", s);
  return so;
}

void test()
{
  char so[kDisplayWidth+1];  // working buffer for pBin
  long int val = 1;
  do {
    printf("%ld =\t\t%#lx =\t\t0b%s\n", val, val, pBinFill(val, so, '0'));
    val *= 11;  // generate test data
  } while (val < 100000000);
}

输出:

00000001 =  0x000001 =  0b00000000000000000000000000000001
00000011 =  0x00000b =  0b00000000000000000000000000001011
00000121 =  0x000079 =  0b00000000000000000000000001111001
00001331 =  0x000533 =  0b00000000000000000000010100110011
00014641 =  0x003931 =  0b00000000000000000011100100110001
00161051 =  0x02751b =  0b00000000000000100111010100011011
01771561 =  0x1b0829 =  0b00000000000110110000100000101001
19487171 = 0x12959c3 =  0b00000001001010010101100111000011

8

2
这实际上是C运行时库的属性,而不是编译器的属性。 - cjm

7

C标准库中没有格式化输出二进制的功能。printf系列函数支持的所有格式化操作都是用于生成人类可读的文本。


7

也许有些偏题,但如果你只需要用于调试以理解或重现一些二进制操作,您可以尝试使用wcalc(一个简单的控制台计算器)。 使用-b选项,您可以获得二进制输出。

例如:

$ wcalc -b "(256 | 3) & 0xff"
 = 0b11

1
还有其他一些选项,例如:ruby -e 'printf("%b\n", 0xabc)'dc 后跟 2o 再跟上 0x123p 等等。 - lindes

6
以下递归函数可能有用:
void bin(int n)
{
    /* Step 1 */
    if (n > 1)
        bin(n/2);
    /* Step 2 */
    printf("%d", n % 2);
}

8
注意,这个方法不适用于负整数。 - Anderson Freitas

5
我对顶部的解决方案进行了大小和C++性能的优化,得到了以下解决方案:
inline std::string format_binary(unsigned int x)
{
    static char b[33];
    b[32] = '\0';

    for (int z = 0; z < 32; z++) {
        b[31-z] = ((x>>z) & 0x1) ? '1' : '0';
    }

    return b;
}

4
如果你想使用动态内存(通过std::string),那么最好摆脱静态数组。最简单的方法就是去掉static限定符,并将b设置为函数本地变量。 - Shahbaz
((x>>z) & 0x01) + '0' 是足够的。 - Jason C

5
void
print_binary(unsigned int n)
{
    unsigned int mask = 0;
    /* this grotesque hack creates a bit pattern 1000... */
    /* regardless of the size of an unsigned int */
    mask = ~mask ^ (~mask >> 1);

    for(; mask != 0; mask >>= 1) {
        putchar((n & mask) ? '1' : '0');
    }

}

或者将字符值添加0或1到'0'的值; ;)不需要三目运算符。 - Owl

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