c11中嵌套的匿名结构

时间:2017-08-19 05:22:51

标签: clang c11 anonymous-struct

我在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;

但是,当我运行以下代码来测试我的struct

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?

我假设它是因为当我只需要4位数时我使用uint8_t,但我认为使用位域可以解决这个问题。

有没有办法可以修改我的typedef以获得上面所需的属性?

(我知道我可以使用屏蔽和位移来获得所需的值,我只是认为这种语法会更好)

提前致谢!

编辑: 我将我的CMakeList更改为set(CMAKE_C_STANDARD_REQUIRED 11),因为我的意思是有一个C项目而不是c ++,但我的代码仍然没有工作。

2 个答案:

答案 0 :(得分: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)

这将转换为最佳的机器代码,并且即使在endianess上也是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

答案 1 :(得分:1)

在C ++中,访问&#34;非活动&#34;无效。工会成员。见这里:Accessing inactive union member and undefined behavior?

因此,您的代码在C ++中调用未定义的行为,尽管它在C中是合法的。

修复它的一种简单方法是将memcpy()所需的字节放入正确的结构中。您甚至可以使用union的一个实例来初始化文字,然后将memcpy()用于另一个您从中读取的实例 - 它满足C ++标准。