这个K&R示例代码有什么缺失?

3
以下是K&R《C程序设计语言》第8.7节中代码示例的摘录:
typedef long Align;

union header {
    struct {
        union header *ptr;
        unsigned size;
    } s;
    Align x;
};

typedef union header Header;

以下是翻译:

这里是解释性摘录:

为了简化对齐,所有块都是标题大小的倍数,并且标题被正确对齐。这是通过包含所需标题结构和最限制类型的实例的联合来实现的,我们任意地将其设为长。

因此,我理解的是,确保 header 实例占用的字节数是 sizeof(Align) 的倍数。这个目标对我来说非常有意义。

但是考虑这样一种情况:如果 long 是 64 位、int 是 32 位,指针是 64 位。在这种情况下,header.s 将是 64+32 = 96 位。因此,sizeof(header) 将是 96 位,这不符合预期的 64 位的倍数。

在这种情况下,我想可能需要将 Align 定义为其他类型(例如 double)。但我不确定是否完全理解了什么会决定特定架构的选择。它只是“最大”的类型吗?如果 long 是最大的类型,那怎么办 - 它不能按我上面描述的方式工作吗?

谢谢

5个回答

3
编译器将调整 header 的大小,以便从 header[] 数组中的元素有效地检索出 ptrsizex。在大多数架构上,存在 x 意味着 sizeof(header) 将增加到 sizeof(long) 的倍数。

3
一个联合体的大小至少与它的最大成员大小相同。因此,即使sizeof(s)为12且sizeof(long)为8(就像你的例子一样),你仍将有sizeof(header) >= 12。同样,联合体的对齐方式是其任何成员中的最大对齐方式,因此存在long成员可以确保header的对齐方式至少为long的对齐方式,在这种情况下为8。
如果您编译此代码,则几乎肯定会发现sizeof(header)为16,而不是12。原因是,如果您有一个header对象数组,编译器需要确保任何成员的对齐方式正确,并且数组在内存中是连续存储的。为了实现这一点,大小必须是对齐方式的倍数。编译器通过在header对象的末尾添加额外的4个字节来实现这一点。

1

我已经很久没有使用结构体了,但是我确实经常使用它们。主要的原则是按降序排列成员大小。

当时编写代码时,long类型会对齐到最大寻址边界。我相信这仍然是事实,但我已经有很长一段时间没有关注硬件问题了。某些硬件的设计是,除非long类型的地址与long类型的大小对齐,否则无法有效地寻址long变量。

我相信头部将对齐到下一个长整型所支持的最有效寻址边界。这取决于硬件。如果long类型在32位边界上可以被有效访问,那么结构体将适当地对齐;如果long类型只能在64位边界上被有效访问,则结构体将占用128位以便适当对齐。


成员永远不会被重新排列或重新排序 - 这是C标准所禁止的。 相反,编译器可以(并且经常会)在成员之间和最后一个成员之后插入填充字节,以使对齐更有利。 - Adam Rosenfield
@Adam Rosefield:因此,当程序员创建结构体时,他们会按照降序对字段进行对齐。我并不是想暗示编译器会这样做。byte、long、byte、long 会浪费空间。long、long、byte、byte 则更加空间高效。现在,空间效率已经不再是一个问题。 - BillThor

0

严格来说,sizeof(Align)必须大于或等于sizeof(s)。但是现在的编译器真的不在意这个,会自动为您完成工作。

#include <limits.h>
#include <stdio.h>

typedef long Align;

typedef union header {
    struct {
        union header *ptr;  /* 64 bits */
        unsigned size;    /* 32 bits */
    } s;                    /* subtotal 96 */
    Align x;                /* 64 bits -> should be greater than 96 */
} Header;                  /* total 128 bits */

typedef struct hair {
    union header *ptr;  /* 64 bits */
    unsigned size;      /* 32 bits */
} Hair;              /* subtotal 96 */

int main(void)
{
    Header u;
    Hair h;

    printf("char %ld bits, %ld bytes\n", sizeof(char)*CHAR_BIT,(sizeof(char)*CHAR_BIT)/8);
    printf("unsigned %ld bits, %ld bytes\n", sizeof(unsigned)*CHAR_BIT,(sizeof(unsigned)*CHAR_BIT)/8);
    printf("int %ld bits, %ld bytes\n", sizeof(int) * CHAR_BIT, (sizeof(int) * CHAR_BIT)/8);
    printf("long int %ld bits, %ld bytes\n", sizeof(long) * CHAR_BIT, (sizeof(long) * CHAR_BIT)/8);
    printf("long long int %ld bits, %ld bytes\n", sizeof(long long) * CHAR_BIT, (sizeof(long long) * CHAR_BIT)/8);
    printf("float %ld bits, %ld bytes\n", sizeof(float) * CHAR_BIT, (sizeof(float) * CHAR_BIT)/8);
    printf("double %ld bits, %ld bytes\n", sizeof(double) * CHAR_BIT, (sizeof(double) * CHAR_BIT)/8);
    printf("long double %ld bits, %ld bytes\n\n", sizeof(long double) * CHAR_BIT, (sizeof(long double) * CHAR_BIT)/8);
    printf("Header u %ld bits, %ld bytes\n", sizeof(u) * CHAR_BIT, (sizeof(u) * CHAR_BIT)/8);
    printf("Header *ptr %ld bits, %ld bytes\n", sizeof(u.s.ptr) * CHAR_BIT, (sizeof(u.s.ptr) * CHAR_BIT)/8);
    printf("Header size %ld bits, %ld bytes\n", sizeof(u.s.size) * CHAR_BIT, (sizeof(u.s.size) * CHAR_BIT)/8);
    printf("Header x %ld bits, %ld bytes\n\n", sizeof(u.x) * CHAR_BIT, (sizeof(u.x) * CHAR_BIT)/8);
    printf("Hair h %ld bits, %ld bytes\n", sizeof(h) * CHAR_BIT, (sizeof(h) * CHAR_BIT)/8);
    printf("Hair *ptr %ld bits, %ld bytes\n", sizeof(h.ptr) * CHAR_BIT, (sizeof(h.ptr) * CHAR_BIT)/8);
    printf("Hair size %ld bits, %ld bytes\n", sizeof(h.size) * CHAR_BIT, (sizeof(h.size) * CHAR_BIT)/8);
    return 0;
}

输出结果为:

$ ./union-limits 
char 8 bits, 1 bytes
unsigned 32 bits, 4 bytes
int 32 bits, 4 bytes
long int 64 bits, 8 bytes
long long int 64 bits, 8 bytes
float 32 bits, 4 bytes
double 64 bits, 8 bytes
long double 128 bits, 16 bytes

Header u 128 bits, 16 bytes
Header *ptr 64 bits, 8 bytes
Header size 32 bits, 4 bytes
Header x 64 bits, 8 bytes

Hair h 128 bits, 16 bytes
Hair *ptr 64 bits, 8 bytes
Hair size 32 bits, 4 bytes

正如您所看到的,Hair与Header的大小相同。但对于不能完成此工作的编译器,正确的方法是使用long double作为Align。

注意保重, Beco。


0

我已经很久(太久了)没有读过K&R书了,但我相信你是对的。在所涉及的平台上,您需要将Align typedef为至少指针大小+无符号整数大小。

我猜想当这篇文章被写出来时(并且最后更新时),32位操作系统占据主导地位,而您的情况逃脱了作者和编辑的注意。当然,他们比我聪明/明智,我可能错了。


更糟糕的是,当K&R最后一次更新时,“(int)”仍然是16位。(嗯,在ANSI之前的K&R版本中。我想知道这是否被重新审查过。) - geekosaur

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接