“位字段在某些机器上从左到右分配,而在其他机器上从右到左分配”-无法从“C编程语言”一书中获得概念

bq9c1y66  于 2023-08-03  发布在  其他
关注(0)|答案(3)|浏览(125)

我正在阅读Kernighan和里奇的“C编程语言”。在讨论该部分末尾的位域时,作者说:
“在某些机器上,字段从左到右分配,而在其他机器上,字段从右到左分配。这意味着,尽管字段对于维护内部定义的数据结构很有用,但在挑选外部定义的数据时,必须仔细考虑先到哪一端的问题;依赖于这些东西的程序是不可移植的。”

    • The C Programming Language [2e] by Kernighan &里奇**[Section 6.9,p.150]*

严格地说,我不明白这几行字的意思。谁能给我一个可能的解释?
PS:我学过计算机组织和体系结构课程。我知道计算机是如何处理比特和字节的。在计算机系统中,信息的最小单位是一个比特,它可以是0也可以是1。8个这样的比特形成一个字节。存储器是字节可寻址的,这意味着存储器中的每个字节都有一个与其相关联的地址。但通常,处理器的字长为2字节(非常旧的系统),4字节,8字节…这意味着在一个内存周期中,CPU可以从主内存中占用一个字长的字节数,并将其放入其寄存器中。这些字节在寄存器中的位置取决于系统的字节顺序。
但我不明白作者所说的“从左到右”或“从右到左”是什么意思。这些词看起来像是与endianness有关,但endianness取决于CPU,而C编译器与此无关。我想到的问题是“什么”的“从左到右”?作者指的是什么物体?

tktrz96b

tktrz96b1#

当一个结构包含位域时,C实现使用一些存储单元来保存它们(如果需要,可以使用多个存储单元)。存储单元可能是一个8位字节,也可能是4个字节,或者是其他大小-这是每个C实现所做的决定。C标准只要求它是可寻址的,这实际上意味着它必须是一个整数字节。
一旦我们有了一个存储单元,它就是一些比特。假设它是32位,并且从31到0对位进行编号,其中,如果我们认为位表示二进制数,则位0表示20,位31表示231。注意,Kernighan和里奇在这里使用“左”和“右”是不精确的。没有固有的左或右。我们通常将数字的最高有效位写在左边,所以我们可以认为第31位是最左边,第0位是最右边。
现在我们有了一个存储单元,它有一定数量的位和这些位的一些标签(31到0或从左到右)。假设你想在它们里面放两个位域,比如宽度为7和5的域。
第31位至第0位中的哪7位用于第一个字段?第二个字段使用哪5个比特?
我们可以将第31-25位用于第一字段,将第24-20位用于第二字段。或者,我们可以将比特6-0用于第一字段,而将比特11-7用于第二字段。
理论上,我们还可以将位27-21用于第一字段,将位15-11用于第二字段。然而,C标准确实规定6.7.2.1。因此,如果C实现将第一字段放在位31-25中,则需要将第二字段放在位24-20中。相反,如果它将第一字段放在比特6-0中,则它必须将第二字段放在比特11-7中。
因此,C标准要求实现从左到右或从右到左排列存储单元中的连续位字段,但它没有说是哪一个。
(我在标准中没有看到任何内容说第一个字段必须从存储单元的一端或另一端开始,而不是中间的某个地方。这将导致浪费一些比特。)

lmyy7pcs

lmyy7pcs2#

当你写:

struct {
    unsigned int version: 4;
    unsigned int length: 4;
    unsigned char dcsn;

字符串
你最终会遇到一个你没有预料到的大麻烦,因为你的代码是不可移植的。
version设置为4,将length设置为5时,某些系统可能会将结构的第一个字节设置为0x45,而其他系统可能会将结构的第一个字节设置为0x54。
当我上大学的时候,这个东西是#ifdef的,如下所示(不正确):

struct {
#if BIG_ENDIAN
    unsigned int version: 4;
    unsigned int length: 4;
#else
    unsigned int length: 4;
    unsigned int version: 4;
#endif
    unsigned char dcsn;


但这仍然是掷骰子,因为没有规则表明位字段中字节的位顺序与机器中字的字节顺序相对应。当你交叉编译时,结构体中的位顺序来自主机的规则,而整数的位顺序来自目标机器的规则(这是必须的),我不会感到惊讶。理论上,代码可以通过为BIG_ENDIAN_BITFIELD设置单独的#ifdef来纠正,但我从未见过这样做。

js5cn81o

js5cn81o3#

下面是一些演示代码。唯一的目的是证明你在问什么。清洁编码等被忽视了

#include <stdio.h>
#include <stdint.h>

union
{
    uint32_t Everything;
    struct 
    {
        uint32_t FirstMentionedBit : 1;
        uint32_t FewOTherBits      :30;
        uint32_t LastMentionedBit  : 1;
    } bitfield;
} Demonstration;

int main()
{
    Demonstration.Everything               =0;
    Demonstration.bitfield.LastMentionedBit=1;
    
    printf("%x\n", Demonstration.Everything);

    Demonstration.Everything                =0;
    Demonstration.bitfield.FirstMentionedBit=1;
    
    printf("%x\n", Demonstration.Everything);

    return 0;
}

字符串
如果在这里使用https://www.tutorialspoint.com/compile_c_online.php,则输出为

80000000
1


但在其他环境中,它可能很容易

1
80000000


这是因为编译器可以自由地将第一个提到的位视为MSB或LSB,并且相应地将最后提到的位视为LSB或MSB。
这就是你引用的描述。

相关问题