是否可以创建两个 uint8_t 指针指向 uint16_t 指针指向的值的前半部分和后半部分?

Is it possible to create two uint8_t pointers to the first and second half of a value pointed to by a uint16_t pointer?

我正在尝试用 C 编写 Gameboy 模拟器,目前正在决定如何实现以下行为:

例如寄存器A和F是8位的寄存器,可以合起来作为16位的寄存器AF。然而,当寄存器 A 和 F 的内容发生变化时,这些变化应该反映在随后对寄存器 AF 的引用中。

如果我将寄存器AF实现为uint16_t*,我可以将寄存器A和F的内容存储为uint8_t*分别指向寄存器AF的第一个和第二个字节吗?如果没有,任何其他建议将不胜感激:)

编辑:澄清一下,这是与 Z80 非常相似的架构

使用联合。

union b
{
    uint8_t a[2];
    uint16_t b;
};

成员a和b共享字节。当在成员 a 中写入一个值,然后使用成员 b 读取该值时,该值将以此类型重新解释。这可能是一个陷阱表示,会导致未定义的行为,但类型 uint8_t 和 uint16_t 没有它们。

另一个问题是字节顺序,写入成员 a 的第一个元素将始终更改成员 b 的第一个字节,但取决于字节顺序,该字节可能代表最高或最低有效位b,因此结果值会因架构而异。


为了避免陷阱表示和字节顺序,而只使用类型 uint16_t 并使用按位运算写入它。例如写入最高8位:

uint16_t a = 0;
uint8_t b = 200;
a =  ( uint16_t )( ( ( unsigned int )a & 0xFF ) | ( ( unsigned int )b << 8 ) ) ;

最低有效 8 位也类似:

a =  ( uint16_t )( ( ( unsigned int )a & 0xFF00 ) | ( unsigned int )b );

这些操作应该放在一个函数中。

( unsigned int ) 的转换是为了避免整数提升。如果 INT_MAX 等于 2^15-1,并且有符号整数的陷阱表示,则操作:b << 8 在技术上可能会导致未定义的行为。

您当然可以使用指针指向任何您想要的地方。

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

int main() {
    uint16_t a=1048;
    uint8_t *ah=(uint8_t*)&a,*al=((uint8_t*)&a)+1;
    printf("%u,%u,%u\n",a,*ah,*al);
}

您需要注意评论中提到的字节序 ( 我相信 gameboy 是小端,而 x86 是大端)。

当然,正如大多数人建议的那样,您应该使用 union,这将使您的代码更不容易因指针地址计算而出错

你可以这样解决:

volatile uint16_t afvalue = 0x55AA;   // Needs long lifetime.

volatile uint16_t *afptr = &afvalue;
volatile uint8_t  *aptr  = (uint8_t *)afptr;
volatile uint8_t  *fptr  = aptr + 1;

if (*aptr == 0xAA) {         // Take endianness into account.
  aptr++;                    // Swap the pointers.
  fptr--;
}

afvalue = 0x0000;

现在aptr指向高位,fptr指向低位,无论模拟器运行在小端还是大端机器上。

注意:由于这些指针类型不同,编译器可能不知道它们指向同一内存,从而优化代码,以便以后读取时不会看到对 *afptr 的写入*aptr。因此 volatile.