0

我正在学习 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 :( 请帮助

提前致谢

4

3 回答 3

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

应该

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

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

于 2013-05-17T14:54:25.140 回答
3

我注意到几点:

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

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

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/

于 2013-05-17T15:15:59.533 回答
1

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

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

C 中,sizeof (char)始终为1,因为运算符告诉您使用多少个 s 来表示一个类型。例如。告诉你用多少个 s 来表示s。就是这样的变化。因此,您的代码不应依赖于a 类型。sizeofcharsizeof (int)charintCHAR_BITsizeof

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


为他们的应用程序选择变量的类型,可移植。如果您的应用程序不需要超出该范围的值,那么int就可以了。否则,您可能需要考虑使用 a long int,它可以便携地存储(包括)-2147483647 和 2147483647 之间的值。如果您需要超出此范围的值,请使用 a 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; }

...然后构造一个 8 位块的数组abs_valand sign,合并在一起。我们已经决定使用可移植决策,我们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,因此值得使用二元运算符。符号和绝对值的提取基本上保持不变,唯一的变化是变量的类型。我们仍然不需要循环,因为我们决定只关心可移植表示的值。这是我从 a 转换为 along 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类型......并打开地板征求意见:

于 2013-05-17T15:58:12.773 回答