是否可以创建两个 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 模拟器,目前正在决定如何实现以下行为:
- 两个 8 位寄存器可以组合起来作为一个 16 位寄存器处理
- 更改配对中 8 位寄存器之一的值应更改组合寄存器的值
例如寄存器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
.
我正在尝试用 C 编写 Gameboy 模拟器,目前正在决定如何实现以下行为:
- 两个 8 位寄存器可以组合起来作为一个 16 位寄存器处理
- 更改配对中 8 位寄存器之一的值应更改组合寄存器的值
例如寄存器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
.