如何在C中输入双关语

时间:2019-03-29 14:37:25

标签: c emulation type-punning z80

关注Casting behavior in C

中的扩展讨论

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

这是我要使用的逻辑:

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

为什么这是不正确的,以及我如何正确地做到这一点(如果需要,可以使用type-punning)?

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

对于那些我还没有提到的人:我知道这是假设的低端架构。我已经完全解决了。

4 个答案:

答案 0 :(得分:7)

这是不正确的,因为b的类型为uint8_t,并且指向uint16_t的指针不能用于访问此类变量。它可能未正确对齐,并且是strict aliasing violation

但是,自(6.7.2.1/15)以来,您可以自由执行(uint8_t *)&regs(struct reg_t*)&regs->b

  

经过适当转换的指向结构对象的指针指向其初始成员,反之亦然。


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

关于如何正确输入pun,请使用并集:

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字节。

答案 1 :(得分:3)

要模拟可作为两个8位寄存器或一个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];

然后,像访问b一样,访问cregs->s.b时必须包括其名称。但是,C具有一项功能,允许您为此使用不带名称的声明。

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

答案 2 :(得分:3)

正确的方法是通过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 ++编译器仍然有效...

答案 3 :(得分:-2)

在C语言中进行双打的正确方法(或为此做几乎任何事情)是使用一种配置为适合于自己预期目的的实现。该标准特意允许旨在用于各种目的的实现方式以使其不适用于其他目的的方式运行。作者认为,从来没有打算建议那些行为不是标准强制要求的程序(而是将在其预期的实现中定义)的程序应该被视为“中断”。作者寻求支持其客户需求的编译器将认识到简单的类型转换构造,无论标准是否要求他们这样做,而作者鄙视其客户需求的优化器也不应被信任来可靠地处理任何复杂的事情。