Вложенные анонимные структуры в 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++.

Другие вопросы по тегам