我怎样才能正确输入双关语?

How can I correctly type-pun?

中扩展讨论的跟进

我正在尝试用 C 语言模拟 Z80,其中可以组合多个 8 位寄存器来创建 16 位寄存器。

这是我尝试使用的逻辑:

struct {
    uint8_t b;
    uint8_t c;
    uint16_t *bc;
} regs[1];
...
regs->bc = (uint16_t *)&(regs->b);

为什么这是不正确的,我怎样才能正确地做到这一点(如果需要使用类型双关)?

我需要多次执行此操作,最好是在同一结构中。

要模拟可以作为两个八位寄存器或一个 16 位寄存器访问的硬件寄存器,您可以使用:

union
{
    struct { int8_t b, c; };
    int16_t bc;
} regs[1];

那么regs->bc就是16位寄存器,regs->bregs->c就是8位寄存器。

注意:这使用匿名 struct,因此 bc 看起来就像他们是工会成员一样。如果 struct 有一个名字,像这样:

union
{
    struct { int8_t b, c; } s;
    int16_t bc;
} regs[1];

那么您在访问 bc 时必须包含其名称,就像 regs->s.b 一样。但是,C 有一个特性,允许您为此目的使用没有名称的声明。

另请注意,这需要 C 编译器。 C 允许使用联合来重新解释数据。 C++ 有不同的规则。

这是不正确的,因为 buint8_t 类型,指向 uint16_t 的指针不能用于访问这样的变量。它可能没有正确对齐,它是 strict aliasing violation.

但是您可以自由选择 (uint8_t *)&regs(struct reg_t*)&regs->b,因为 (6.7.2.1/15)

A pointer to a structure object, suitably converted, points to its initial member and vice versa.


在进行与硬件相关的编程时,请确保永远不要使用有符号类型。这意味着将 intn_t 更改为 uintn_t.

至于如何正确输入双关语,请使用并集:

typedef union
{
  struct                 /* standard C anonymous struct */
  {
    uint8_t b;
    uint8_t c;
  };
  uint16_t bc;
} reg_t;

然后您可以将其分配给一个 16 位硬件寄存器,如下所示:

volatile reg_t* reg = (volatile reg_t*)0x1234;

其中0x1234是硬件寄存器地址。

注意:此联合依赖字节顺序。 b 将在大端系统上访问 bc 的 MS 字节,但在小端系统上访问 bc 的 LS 字节。

正确的方法是通过 C 中的匿名联合,如其他答案中所示。但是当你想处理字节时,你可以在严格的别名规则中使用特殊的字符处理:无论是什么类型,使用 char 指针访问其表示的字节总是合法的。所以这是符合 C

struct {
    uint16_t bc;
    uint8_t *b;
    uint8_t *c;
} regs[1];

regs->b = (uint8_t *) &(regs->bc);
regs->c = regs->b + 1

有趣的是,它对 C++ 编译器仍然有效...

在 C 中输入双关语(或做几乎任何事情)的正确方法是使用配置为适合预期目的的实现。该标准故意允许旨在用于各种目的的实现以不适合其他目的的方式运行。根据作者的说法,从来没有打算建议那些行为不是标准强制要求的程序(但将在它们预期的实现中定义)应该被视为 "broken"。其作者寻求支持其客户需求的编译器将识别直截了当的类型双关结构,无论标准是否要求他们这样做,而作者蔑视其客户需求的优化器不应该被信任可靠地处理任何复杂的事情。