【问题标题】:Bit vector operations and Endianess位向量操作和字节序
【发布时间】:2014-04-23 19:42:49
【问题描述】:

我在我的软件中做了很多位向量操作。例如:假设我需要存储关于候选人“n”的布尔信息,我执行以下操作:

uint64_t *information_vector;
uint32_t pos = n / 64;
uint32_t bit_pos = n % 64;

information_vector[pos] |= (1 << bit_pos);

我在阅读该信息时遵循类似的程序:

uint32_t pos = n / 64;
uint32_t bit_pos = n % 64;
if (information_vector[pos] & (1 << bit_pos)) {
       // do something
}

同时,我还将information_vector写入磁盘并再次读取。现在,我正在尝试解决一个让我做噩梦的错误,让我觉得 Endianess 可能是这里的罪魁祸首,但我无法解释。有什么办法可以查吗?这种位向量操作通常是字节序安全且跨架构的吗?

我还看到,在代码的某处,我在另一个位向量中为同一候选者设置了一些其他信息:

uint8_t byte_position = n / 8;
uint8_t bit_position = n % 8;
another_information_vector[byte_position] |= (1 << bit_position);

我通常通过对这些位向量进行与运算来找到一组共同的属性。

【问题讨论】:

  • 它是安全的,除非您已将其编译为一个字节序并部署在另一个字节序上

标签: c bit-manipulation endianness bitvector


【解决方案1】:

在大多数情况下,最安全的变体是在字节级别上进行操作,因此除数为 8。OTOH 在某些情况下可能不是最理想的。与字访问相比,有些架构不能直接访问字节,或者访问成本很高。

在一个小型机器上,选择任何合理的除数(8,16,32,64)时,相同的方法工作不变。例如,对于位索引 22,字节级访问处理索引为 2 的字节的编号为 6 的位;短字访问用 1 处理短字的第 6 位;等等。

在大端机器上,这需要用1 &lt;&lt; (BITS_PER_CELL-1-bit_position) 或(相同的)HIGHEST_BIT &gt;&gt; bit_position 替换1 &lt;&lt; bit_position,其中 HIGHEST_BIT 为 0x80 用于 uint8_t,0x80000000 用于 uin32_t 等。并且,位索引 0 将表示字节 0 的 MSB,而不是 little-endian 的情况,它表示字节 0 的 LSB。

(在串行线路上可以看到类似的效果。在 RS232 或以太网中,字节从 LSB 传输到 MSB。MAC 地址中的单个/组位是线路上的第一个位,但它是第一个八位字节的 LSB .)

【讨论】:

    【解决方案2】:

    这在 CPU 内的架构中肯定是字节序安全的。从一种架构写入磁盘,然后在不同的架构上读取它取决于您如何将其读取和写入磁盘。这与将任何多字节数写入磁盘并将其读回时遇到的问题没有什么不同。两端都必须对该数字进行相同的解释。如果在此示例中您只是将 8 个字节写入磁盘,然后在不同的字节序架构上读取它们,那么您将交换字节。

    【讨论】:

      【解决方案3】:

      一般来说,如果您始终使用相同类型(在您的情况下为 uint64_t)访问您的位向量,并且您访问数据的所有系统的字节序都相同,那么字节序将不会成为问题。

      不过,让自己放心的最简单方法是将对象的地址转换为 char* 并取消引用,这样您就可以按照它们在内存中的排列顺序一次看到一个字节。

      更新:我刚刚观察到您的第三个代码块似乎通过执行 n % 8 来计算 byte_position

      如果您有时写出 uint64_t 的数组,有时将其视为 uint8_t 的数组,那么如果您的系统是 little endian,您的结果可能会出乎意料。

      避免此问题的最佳方法是保持类型一致。

      To make this problem more concrete, consider the following example:
      
      #include <stdio.h>
      #include <stdint.h>
      
      int main(){
          uint64_t myVector = 1 << 2; // set second bit of LSB
          uint8_t * ptr = (uint8_t *) &myVector;
          int i;
          for (i = 0; i < 8; i++)
             printf("%x\n", ptr[i]);
      }
      

      在我的 little-endian x86 系统上,这将打印 4 后跟 7 个 0,因为最高有效字节存储在 uint64_t 的最高地址处。如果您习惯于考虑从最高位到最低位,从左到右排列的位,这可能与您的直觉背道而驰。

      【讨论】:

      • 写入磁盘和返回也会生效吗?
      • @aryan,通常写入磁盘没有区别,除非您以uint64_t 写入并以uint8_t 读取,因为这实际上与重新解释内存相同。
      • @aryan,我假设您在同一台机器或至少相同的机器架构上读写。
      • 如果您尝试在具有不同字节序的平台上读取相同的字节,字节序将是一个问题。即从 little-endian intel box 发送 80_00_00_00_00_00_00_00 到 big-endian powerPC 机器,相同的字节将被完全不同的解释。
      • @merlin2011 是的,我在同一台机器上读写。
      猜你喜欢
      • 2020-05-22
      • 1970-01-01
      • 1970-01-01
      • 2015-07-30
      • 1970-01-01
      • 2011-10-14
      • 2015-03-21
      • 2021-06-26
      • 1970-01-01
      相关资源
      最近更新 更多