【问题标题】:simple bit manipulation fails简单的位操作失败
【发布时间】:2025-11-30 01:30:01
【问题描述】:

我正在学习 C 中的位操作,并且我编写了一个简单的程序。然而程序失败了。有人可以看看这段代码吗? 基本上我想提取一个 4 字节的“长”变量并将其重新组合到它的单个字节中,反之亦然。这是我的代码:

    printf("sizeof char= %d\n", sizeof(char));
    printf("sizeof unsigned char= %d\n", sizeof(unsigned char));
    printf("sizeof int= %d\n", sizeof(int));
    printf("sizeof long= %d\n", sizeof(long));
    printf("sizeof unsigned long long= %d\n", sizeof(unsigned long long));

    long val = 2;
    int k = 0;
    size_t len = sizeof(val);
    printf("val = %ld\n", val);
    printf("len = %d\n", len);

    char *ptr;
    ptr = (char *)malloc(sizeof(len));
    //converting 'val' to char array
    //val = b3b2b1b0 //where 'b is 1 byte. Since 'long' is made of 4 bytes, and char is 1 byte, extracting byte by byte of long into char
    //do{
        //val++;
    for(k = 0; k<len; k++){
        ptr[k] = ((val >> (k*len)) && 0xFF);
        printf("ptr[%d] = %02X\n", k,ptr[k]);
    }
    //}while(val < 12);

    //reassembling the bytes from char and converting them to long
    long xx = 0;
    int m = 0;
    for(m = 0; m< len; m++){
        xx = xx |(ptr[m]<<(m*8));
    }
    printf("xx= %ld\n", xx);

为什么我没有看到 xx 返回 2?此外,无论 'val' 的值如何,ptr[0] 似乎都存储 1 :( 请帮忙

提前致谢

【问题讨论】:

  • 您的内存分配似乎有错误:ptr = (char *)malloc(sizeof(len)); 应该是 ptr = (char *)malloc(len); 否则您将为 size_t 值分配空间,而不是 long 值。在您的情况下,这可能是相同的大小,但这仍然是一个疏忽。
  • 你关心便携性吗?
  • 哦,是的......我应该怎么做才能改进这段代码?

标签: c bit-manipulation


【解决方案1】:
ptr[k] = ((val >> (k*len)) && 0xFF);

应该是

ptr[k] = ((val >> (k*8)) & 0xFF);

&amp;&amp; 用于条件语句和 & 用于按位与。 此外,当您将值拆分为字符时,您希望循环的每次迭代都使用与字节中一样多的位进行移位。这几乎总是 8,但也可以是别的东西。头文件limits.h 有相关信息。

【讨论】:

  • (k*8) 似乎更合适,因为 OP 提到 long 在他的机器上是 4 个字节(即 len 将是 4 个字节)。
  • 谢谢西蒙。但是我无法提取大于一个字节的内容。例如 long value(val) 256 给了我奇怪的 xx 值。
  • @Michael:因为我要移动一个字节K次,所以我必须将lne改为8。不是吗?
【解决方案2】:

我注意到一些事情:

  1. 您使用的是布尔 && 运算符而不是按位 &
  2. 您正在移动“k*len”而不是“k*8”
  3. 您正在使用“sizeof(len)”分配数组,而不仅仅是“len”
  4. 您使用的是“char”而不是“unsigned char”。这会使“(ptr[m]

因此,您的代码的固定版本将是:

printf("sizeof char= %d\n", sizeof(char));
printf("sizeof unsigned char= %d\n", sizeof(unsigned char));
printf("sizeof int= %d\n", sizeof(int));
printf("sizeof long= %d\n", sizeof(long));
printf("sizeof unsigned long long= %d\n", sizeof(unsigned long long));

long val = 2;
int k = 0;
size_t len = sizeof(val);
printf("val = %ld\n", val);
printf("len = %d\n", len);

unsigned char *ptr;
ptr = (unsigned char *)malloc(len);
//converting 'val' to char array
//val = b3b2b1b0 //where 'b is 1 byte. Since 'long' is made of 4 bytes, and char is 1 byte, extracting byte by byte of long into char
//do{
    //val++;
for(k = 0; k<len; k++){
    ptr[k] = ((val >> (k*8)) & 0xFF);
    printf("ptr[%d] = %02X\n", k,ptr[k]);
}
//}while(val < 12);

//reassembling the bytes from char and converting them to long
long xx = 0;
int m = 0;
for(m = 0; m< len; m++){
    xx = xx |(ptr[m]<< m*8);
}
printf("xx= %ld\n", xx);

另外,在未来,这样的问题会更适合https://codereview.stackexchange.com/

【讨论】:

  • 谢谢泰勒。我想我现在似乎有问题。这段代码在移植到 ARM 系统时不起作用:( ptr printf 语句上的值显示奇怪的值。这可能是字节序问题吗?
【解决方案3】:

正如其他人现在提到的那样,我不确定ptr[k] = ((val &gt;&gt; (k*len)) &amp;&amp; 0xFF); 是否符合您的要求。 &amp;&amp; 运算符是一个布尔运算符。如果(value &gt;&gt; (k*len)) 是某个非零值,而0xFF 是某个非零值,则存储到ptr[k] 中的值将为1。这就是布尔运算符的工作方式。也许您打算使用&amp; 而不是&amp;&amp;

此外,您已选择使用移位运算符,它适用于 unsigned 类型,但对于有符号类型有多种不可移植的方面。 xx = xx |(ptr[m]&lt;&lt;(m*8)); 可能会调用未定义的行为,例如,因为它看起来可能导致有符号整数溢出。

在 C 中,sizeof (char)总是为 1,因为 sizeof 运算符告诉您有多少 chars 用于表示一个类型。例如。 sizeof (int) 告诉您有多少 chars 用于表示 ints。改变的是CHAR_BIT。因此,您的代码不应依赖于 sizeof 类型。

事实上,如果您希望您的代码具有可移植性,那么您不应该期望能够在 int 中存储大于 32767 或小于 -32767 的值。这与大小无关,因为可能存在填充位。总结一下:sizeof 类型不一定反映它可以存储的值集!


为其应用选择变量类型,可移植。如果您的应用程序不需要超出该范围的值,那么int 就可以了。否则,您可能需要考虑使用long int,它可以便携地存储(包括)-2147483647 和 2147483647 之间的值。如果您需要超出此范围的值,请使用 long long int,这将为您提供至少包含 -9223372036854775807 和 9223372036854775807 之间的值的保证范围。超出此范围的任何值都可能需要一个多精度算术库,例如 GMP

当您不希望使用负值时,您应该使用unsigned 类型。

考虑到整数类型的可移植性选择,现在可以设计一种可移植的方式将这些整数写入文件,并从文件中读取这些整数。您需要将符号和绝对值提取到unsigned int

unsigned int sign = val < 0; /* conventionally 1 for negative, 0 for positive */
unsigned int abs_val = val;
if (val < 0) { abs_val = -abs_val; }

...然后构造一个由abs_valsign组成的8位块数组,合并在一起。我们已经决定使用便携式决策,我们的int 只能存储 16 位,因为我们只在其中存储 -32767 和 32767 之间的值。因此,不需要循环或按位移位。我们可以使用乘法来移动我们的符号位,并使用除法/取模来减少我们的绝对值。考虑到符号通常与最高有效位一起出现,它位于数组的开头(大端)或结尾(小端)。

unsigned char big_endian[] = { sign * 0x80 + abs_val / 0x100,
                               abs_value % 0x100 };
unsigned char lil_endian[] = { abs_value % 0x100,
                               sign * 0x80 + abs_val / 0x100 };

为了反转这个过程,我们执行相反的操作(即用除法和模代替乘法,用乘法代替除法和加法,提取符号位并重新计算值):

unsigned int big_endian_sign = array[0] / 0x80;
int big_endian_val = big_endian_sign
                   ? -((array[0] % 0x80) * 0x100 + array[1])
                   :  ((array[0] % 0x80) * 0x100 + array[1]);

unsigned int lil_endian_sign = array[1] / 0x80;
int lil_endian_val = lil_endian_sign
                   ? -((array[1] % 0x80) * 0x100 + array[0])
                   :  ((array[1] % 0x80) * 0x100 + array[0]);

long 的代码稍微复杂一些,因此值得使用二元运算符。符号和绝对值的提取基本保持不变,唯一的变化是变量的类型。我们仍然不需要循环,因为我们决定只关心可移植表示的值。以下是我如何从 long val 转换为 unsigned char[4]

unsigned long sign = val < 0; /* conventionally 1 for negative, 0 for positive */
unsigned long abs_val = val;
if (val < 0) { abs_val = -abs_val; }

unsigned char big_endian[] = { (sign << 7) | ((abs_val >> 24) & 0xFF),
                               (abs_val >> 16) & 0xFF,
                               (abs_val >> 8) & 0xFF,
                               abs_val & 0xFF };
unsigned char lil_endian[] = { abs_val & 0xFF,
                               (abs_val >> 8) & 0xFF,
                               (abs_val >> 16) & 0xFF,
                               (sign << 7) | ((abs_val >> 24) & 0xFF) };

...这是我将如何转换回有符号值的方法:

unsigned int big_endian_sign = array[0] >> 7;
long big_endian_val = big_endian_sign
                   ? -((array[0] & 0x7F) << 24) + (array[1] << 16) + (array[2] << 8) + array[3]
                   :  ((array[0] & 0x7F) << 24) + (array[1] << 16) + (array[2] << 8) + array[3];

unsigned int lil_endian_sign = array[3] >> 7;
long lil_endian_val = lil_endian_sign
                   ? -((array[3] & 0x7F) << 24) + (array[2] << 16) + (array[1] << 8) + array[0]
                   :  ((array[3] & 0x7F) << 24) + (array[2] << 16) + (array[1] << 8) + array[0];

我会让你为 unsignedlong long 类型设计一个方案......并为 cmets 开辟空间:

【讨论】:

  • 感谢您提供如此详细的解释。我想知道如果“val”是无符号的但长度为 8 个字节会发生什么。那么在 C 中使用的数据类型是什么?我想到了 unsigned long long 但我怎么知道我的编译器是否支持它?我在一些论坛上看到 C90 和 C99 编译器以不同的方式处理它们。我的是一个 ARM C 编译器。如何检查?
  • @user900785 在提出这些问题之前,您正在阅读哪本手册?您的编译器有一本手册,其中会提到它所遵循的 C 标准。所有符合 C99 和 C11 的编译器都允许使用 unsigned long long。不要按字节计算,因为正如我之前所说的填充位可以存在。按范围走。 unsigned long long 将能够表示(包括)至少 0 .. 18446744073709551615 (2 ^ 64 - 1) 之间的值。