Вложенные анонимные структуры в c11
Я пишу интерпретатор CHIP-8 в c11 для забавы, и я подумал, что было бы здорово декодировать коды операций, используя анонимные структуры.
В идеале у меня был бы тип, где, если бы у меня был код операции 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++, однако мой код все еще не работает.
2 ответа
Я бы пропустил все, что называется битовыми полями, поскольку они нестандартные и непереносимые. Что произойдет, когда вы будете использовать битовые поля в 8- или 16-битных типах stdint.h, никто не знает. Кроме того, вы получаете проблемы с заполнением из-за структур. И ваш код будет зависеть от endianess. В целом плохая идея (но, конечно, хорошо, только для любителей).
Вместо этого я бы просто определил тип как:
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
В C++ недопустим доступ к "неактивным" членам объединения. Смотрите здесь: Доступ к неактивному члену объединения и неопределенному поведению?
Таким образом, ваш код вызывает неопределенное поведение в C++, хотя это будет допустимо в C.
Простой способ исправить это memcpy()
байты вам нужно в правильную структуру. Вы можете даже использовать один экземпляр объединения для инициализации литералом, а затем memcpy()
это в другой экземпляр, из которого вы затем читаете - который удовлетворяет стандарту C++.