打印数字的二进制表示

tha*_*i03 6 c bit-manipulation bit

我想打印一个二进制表示int.我的解决方案似乎对工作都intunsigned int在Visual Studio中,但有人告诉我,这是错误的.有人看到错误吗?如果是这样,为什么我的程序似乎对我有用?

void printbin(int n)
{
    unsigned int i = 1<<31;

    for (int j=0; j<32; j++)
    {
        if ((n & i) != 0)
            printf("1");
        else
            printf("0");
        i = i>>1;
    }

    printf("\n");
}
Run Code Online (Sandbox Code Playgroud)

Joh*_*ger 2

为什么我的程序似乎对我有用?

有两种非排他性的可能性:

  1. 您的程序对于您测试的所有输入和条件都可以正常工作,但有些输入和/或条件您没有测试,但它会失败。作为这种情况的一个特例,抱怨可能是您的程序依赖于未定义的、实现定义的或未指定的行为(确实如此),这使得它本质上是错误的,即使它碰巧在测试环境中按您的预期工作。
  2. 您错误地认为您的程序正常工作,可能是由于对所需输出的误解所致。

未定义/实现定义的行为

1<<31从未定义的行为开始:正如 @chux 首先观察到的,在 32 位(或更小)的系统上计算表达式会产生未定义的行为int,例如 Windows 和 Visual Studio 的 C 编译器提供的系统。两个操作数的类型均为int,因此结果的类型为int,但算术上正确的结果超出了该类型可以表示的值的范围。这种情况下的行为将为无符号整数结果定义,但对于有符号整数类型(例如int. 由于您将结果分配给类型为 的变量unsigned int,因此只需将表达式更改为 即可解决该问题1u<<31

此外,任何类型表示中的位数均未指定,但您的代码假定为 32-bit unsigned int。这确实是 Visual Studio 的 C 编译器提供的 s 的大小unsigned int,但您不需要依赖于此。unsigned int通过计算as表示中的位数,您将获得每个环境的正确的依赖于实现的结果CHAR_BIT * sizeof(unsigned int)

然而,只要我们讨论实现依赖性,对象表示中的所有位都不一定会影响其值。也可以有填充位,并且在类型表示中位少于 32 个的实现中unsigned int,表达式1u << 31或等效项的计算结果为零。为了完全正确, 的表示中unsigned int位数的计算必须基于 的值UINT_MAX。您创建的位掩码的另一种表达式可以避免此问题~(UINT_MAX >> 1)

输出格式

至于输出格式,目前还不清楚 an 的“二进制”形式int是什么,特别是考虑到您想要提供负值和正值。如果您应该在不使用符号的情况下提供负值的形式-(正如您的代码尝试做的那样),则必须指定或假定所需输出形式的详细信息(例如大端序、32 位二进制补码),否则您打算探测输入值的机器特定表示。由于您没有指定特定的格式,如果(部分)问题在于输出格式,那么我只能得出结论,需要特定于机器的表示或符号/大小。

机器表示

如果目标是探测int值的机器表示,那么您的程序至少在两个(附加)计数上是不正确的。

首先,计算表达式涉及将from typen&i的值转换为 type 。因此,您打印的是转换值的表示形式,不能保证与原始值的表示形式相同。但实际上,您不太可能遇到存在实际差异的机器和 C 实现。当然,Windows 上的 Visual Studio 不是这样的环境。iintunsigned intint

然而,此外,您的代码输出的值的逻辑表示形式不一定符合物理表示形式。即使假设您不会遇到各种对象表示形式的转换或大小等问题,您的代码也会假定物理布局是从最高有效字节到最低有效字节。也就是说,它会打印大端表示形式,而不管实际的物理表示形式。int在 x86 和 x86_64 上, s的本机物理表示形式是端,下面我打印机器表示的代码将打印与您的代码不同的结果。

void printbin(int n)
{
    unsigned char *p = (unsigned char *) &n;

    for (int j=0; j < sizeof(n); j++)
    {
        for (unsigned char mask = 1u << (CHAR_BIT - 1); mask; mask >>= 1) {
            putchar((*p & mask) ? '1' : '0');
        }
        p += 1;
    }

    putchar('\n');
}
Run Code Online (Sandbox Code Playgroud)

该标准允许不同指针类型之间的转换,并且它特别规定该程序中的转换将导致p被初始化为指向 表示形式中的第一个字节n。该程序逐步遍历表示形式中的每个字节(通过运算符确定的总数sizeof)并打印每个字节中的位,从最高有效到最低有效,与您的版本类似。如果有填充位,则将其包括在内。

符号/数值表示

另一方面,如果您想要一个有符号的二进制数字字符串,从最高有效非零位到最低有效位,那么您可以这样做:

void printbin_digits(unsigned int n) {
    char bits[CHAR_BIT * sizeof(unsigned int)] = {0};
    int bit_count = 0;

    while (n) {
        bits[bit_count++] = n % 2;
        n >>= 1;
    }
    while (bit_count) {
        putchar(bits[--bit_count] ? '1' : 0);
    }
}

void printbin(int n)
{
    if (n == 0) {
        putchar('0');
    } else if (n == INT_MIN) {
        putchar('-');
        printbin_digits(-(n / 2));
        putchar((n % 2) ? '1' : '0');
    } else if (n < 0) {
        putchar('-');
        printbin_digits(-n);
    } else {
        printbin_digits(n);
    }

    putchar('\n');
}
Run Code Online (Sandbox Code Playgroud)

int这无需对 C 标准不支持的类型值的表示进行任何假设。特别注意当n有值时的特殊处理INT_MIN——它很混乱,但这是必要的,因为计算表达式-INT_MIN可以(并且在 x86 上确实)产生未定义的行为。