c11 中的嵌套匿名结构
Nested Anonymous Structs in c11
我正在用 c11 编写 CHIP-8 解释器是为了好玩,我认为使用匿名结构解码操作码会很酷。
理想情况下,如果我有操作码 opcode_t code = {.bits = 0xABCD}
,我会有一个类型
它应该具有以下属性:
code.I == 0xA
code.X == 0xB
code.Y == 0xC
code.J == 0xD
code.NNN == 0xBCD
code.KK == 0xCD
我想出的结构是:
typedef union
{
uint16_t bits : 16;
struct
{
uint8_t I : 4;
union
{
uint16_t NNN : 12;
struct
{
uint8_t X : 4;
union
{
uint8_t KK : 8;
struct
{
uint8_t Y : 4;
uint8_t J : 4;
};
};
};
};
};
} opcode_t;
但是,当我运行下面的代码来测试我的结构
opcode_t test_opcode = { .bits = 0xABCD };
printf(
"I = %x, X = %x, Y = %x, J = %x, NNN = %x, KK = %x \n",
test_opcode.I,
test_opcode.X,
test_opcode.Y,
test_opcode.J,
test_opcode.NNN,
test_opcode.KK
);
输出是
I = d, X = 0, Y = 0, J = 0, NNN = 0, KK = 0
我正在 Apple LLVM version 8.1.0 (clang-802.0.42)
中编译这段代码
使用以下 CMakeLists.txt:
cmake_minimum_required(VERSION 3.9)
project (Chip8)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set (CMAKE_CXX_STANDARD 11 REQUIRED)
find_package(Curses REQUIRED)
include_directories(${CURSES_INCLUDE_DIR}/src)
add_executable (Chip8 src/main.c src/Chip8State.c)
target_link_libraries(Chip8 ${CURSES_LIBRARIES})
为什么 test_opcode.I == 0xD,为什么其他成员都是 0x0?
我假设这是因为我在使用 uint8_t 时只需要一个 4 位数字,但我认为使用位域可以解决该问题。
有什么方法可以修改我的 typedef 以具有上述所需的属性?
(我知道我可以使用掩码和位移来获得所需的值,我只是认为这种语法会更好)
提前致谢!
编辑:
我将我的 CMakeList 更改为 set(CMAKE_C_STANDARD_REQUIRED 11)
,因为我的意思是有一个 C 项目而不是 C++,但是我的代码仍然无法正常工作。
在 C++ 中,访问联合的 "inactive" 成员是无效的。看这里:Accessing inactive union member and undefined behavior?
所以你的代码在 C++ 中调用了未定义的行为,尽管它在 C 中是合法的。
一个简单的修复方法是 memcpy()
将您需要的字节放入正确的结构中。您甚至可以使用联合的一个实例用文字进行初始化,然后 memcpy()
它到另一个实例,然后您从中读取 - 满足 C++ 标准。
我将跳过所有称为位域的内容,因为它们是非标准且不可移植的。当您在 8 位或 16 位 stdint.h 类型上使用位域时会发生什么,没人知道。此外,由于结构,您还会遇到填充问题。而且您的代码将依赖字节顺序。总的来说是个坏主意(但当然可以,只是出于爱好者的目的)。
相反,我将类型定义为:
typedef uint16_t opcode_t;
然后编写一些访问宏:
#define I(op) ((op & 0xF000u) >> 12)
#define X(op) ((op & 0x0F00u) >> 8)
#define Y(op) ((op & 0x00F0u) >> 4)
#define NNN(op) (op & 0x0FFFu)
#define KK(op) (op & 0x00FFu)
这将转换为尽可能最好的机器代码,并且即使在字节顺序中也是 100% 可移植的。
您甚至可以为通用访问和类型安全发明一些更高级别的宏:
#define GET(op, type) _Generic(op, opcode_t: type(op))
完整示例:
#include <stdint.h>
#include <stdio.h>
#include <inttypes.h>
typedef uint16_t opcode_t;
#define I(op) ((op & 0xF000u) >> 12)
#define X(op) ((op & 0x0F00u) >> 8)
#define Y(op) ((op & 0x00F0u) >> 4)
#define NNN(op) (op & 0x0FFFu)
#define KK(op) (op & 0x00FFu)
#define GET(op, type) _Generic(op, opcode_t: type(op))
int main (void)
{
opcode_t op = 0xABCD;
printf("I\t0x%"PRIX16 "\n", GET(op, I));
printf("X\t0x%"PRIX16 "\n", GET(op, X));
printf("Y\t0x%"PRIX16 "\n", GET(op, Y));
printf("NNN\t0x%"PRIX16 "\n", GET(op, NNN));
printf("KK\t0x%"PRIX16 "\n", GET(op, KK));
}
输出:
I 0xA
X 0xB
Y 0xC
NNN 0xBCD
KK 0xCD
我正在用 c11 编写 CHIP-8 解释器是为了好玩,我认为使用匿名结构解码操作码会很酷。
理想情况下,如果我有操作码 opcode_t code = {.bits = 0xABCD}
它应该具有以下属性:
code.I == 0xA
code.X == 0xB
code.Y == 0xC
code.J == 0xD
code.NNN == 0xBCD
code.KK == 0xCD
我想出的结构是:
typedef union
{
uint16_t bits : 16;
struct
{
uint8_t I : 4;
union
{
uint16_t NNN : 12;
struct
{
uint8_t X : 4;
union
{
uint8_t KK : 8;
struct
{
uint8_t Y : 4;
uint8_t J : 4;
};
};
};
};
};
} opcode_t;
但是,当我运行下面的代码来测试我的结构
opcode_t test_opcode = { .bits = 0xABCD };
printf(
"I = %x, X = %x, Y = %x, J = %x, NNN = %x, KK = %x \n",
test_opcode.I,
test_opcode.X,
test_opcode.Y,
test_opcode.J,
test_opcode.NNN,
test_opcode.KK
);
输出是
I = d, X = 0, Y = 0, J = 0, NNN = 0, KK = 0
我正在 Apple LLVM version 8.1.0 (clang-802.0.42)
使用以下 CMakeLists.txt:
cmake_minimum_required(VERSION 3.9)
project (Chip8)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
set (CMAKE_CXX_STANDARD 11 REQUIRED)
find_package(Curses REQUIRED)
include_directories(${CURSES_INCLUDE_DIR}/src)
add_executable (Chip8 src/main.c src/Chip8State.c)
target_link_libraries(Chip8 ${CURSES_LIBRARIES})
为什么 test_opcode.I == 0xD,为什么其他成员都是 0x0?
我假设这是因为我在使用 uint8_t 时只需要一个 4 位数字,但我认为使用位域可以解决该问题。
有什么方法可以修改我的 typedef 以具有上述所需的属性?
(我知道我可以使用掩码和位移来获得所需的值,我只是认为这种语法会更好)
提前致谢!
编辑:
我将我的 CMakeList 更改为 set(CMAKE_C_STANDARD_REQUIRED 11)
,因为我的意思是有一个 C 项目而不是 C++,但是我的代码仍然无法正常工作。
在 C++ 中,访问联合的 "inactive" 成员是无效的。看这里:Accessing inactive union member and undefined behavior?
所以你的代码在 C++ 中调用了未定义的行为,尽管它在 C 中是合法的。
一个简单的修复方法是 memcpy()
将您需要的字节放入正确的结构中。您甚至可以使用联合的一个实例用文字进行初始化,然后 memcpy()
它到另一个实例,然后您从中读取 - 满足 C++ 标准。
我将跳过所有称为位域的内容,因为它们是非标准且不可移植的。当您在 8 位或 16 位 stdint.h 类型上使用位域时会发生什么,没人知道。此外,由于结构,您还会遇到填充问题。而且您的代码将依赖字节顺序。总的来说是个坏主意(但当然可以,只是出于爱好者的目的)。
相反,我将类型定义为:
typedef uint16_t opcode_t;
然后编写一些访问宏:
#define I(op) ((op & 0xF000u) >> 12)
#define X(op) ((op & 0x0F00u) >> 8)
#define Y(op) ((op & 0x00F0u) >> 4)
#define NNN(op) (op & 0x0FFFu)
#define KK(op) (op & 0x00FFu)
这将转换为尽可能最好的机器代码,并且即使在字节顺序中也是 100% 可移植的。
您甚至可以为通用访问和类型安全发明一些更高级别的宏:
#define GET(op, type) _Generic(op, opcode_t: type(op))
完整示例:
#include <stdint.h>
#include <stdio.h>
#include <inttypes.h>
typedef uint16_t opcode_t;
#define I(op) ((op & 0xF000u) >> 12)
#define X(op) ((op & 0x0F00u) >> 8)
#define Y(op) ((op & 0x00F0u) >> 4)
#define NNN(op) (op & 0x0FFFu)
#define KK(op) (op & 0x00FFu)
#define GET(op, type) _Generic(op, opcode_t: type(op))
int main (void)
{
opcode_t op = 0xABCD;
printf("I\t0x%"PRIX16 "\n", GET(op, I));
printf("X\t0x%"PRIX16 "\n", GET(op, X));
printf("Y\t0x%"PRIX16 "\n", GET(op, Y));
printf("NNN\t0x%"PRIX16 "\n", GET(op, NNN));
printf("KK\t0x%"PRIX16 "\n", GET(op, KK));
}
输出:
I 0xA
X 0xB
Y 0xC
NNN 0xBCD
KK 0xCD