将位域转换为 uint8_t

Turning a bitfield into a uint8_t

我下面的位域代表 6502 CPU 的 7 个状态标志。我正在尝试模拟指令 php,它将状态标志的副本推送到堆栈上。

struct Flags {
    uint8_t C: 1;
    uint8_t Z: 1;
    uint8_t I: 1;
    uint8_t D: 1;
    uint8_t V: 1;
    uint8_t N: 1;
    uint8_t B: 2;
};

我需要一种方法将每个字段打包成单字节数据类型,例如 uint8_t,以便我可以将其压入堆栈。但是,下面的代码给出了这个错误:operand of type 'struct Flags' where arithmetic or pointer type is required。我该如何解决这个问题?

int main() {
    struct Flags f = {0, 0, 0, 0, 1, 0, 0};
    uint8_t f_as_byte = (uint8_t) f;
}

你可以用 union:

typedef unsigned char uint8_t;

struct Flags {
    uint8_t C:1;
    uint8_t Z:1;
    uint8_t I:1;
    uint8_t D:1;
    uint8_t V:1;
    uint8_t N:1;
    uint8_t B:2;
};

union uFlags {
    uint8_t B;
    struct Flags F;
};

int
main()
{
    struct Flags f = { 0, 0, 0, 0, 1, 0, 0 };
    union uFlags u;

    u.F = f;
    uint8_t f_as_byte = u.B;
}

使用指定的初始化程序,您可以直接初始化 union 而无需单独的 f:

typedef unsigned char uint8_t;

struct Flags {
    uint8_t C:1;
    uint8_t Z:1;
    uint8_t I:1;
    uint8_t D:1;
    uint8_t V:1;
    uint8_t N:1;
    uint8_t B:2;
};

union uFlags {
    uint8_t B;
    struct Flags F;
};

int
main()
{
    union uFlags u = {
        .F = { .V = 1 }
    };

    uint8_t f_as_byte = u.B;
}

位域的问题在于它是实现定义的位的排列顺序。这对于 6502 仿真器来说是相当不可接受的。 PHP 命令必须以 精确 所需格式推送状态字,即类似于

uint8_t as_state = f.N << 7 | f.V << 6 | f.B << 4 | f.D << 3 | f.I << 2 | f.Z << 1 | f.C;

你的布局有误,B成员位置错误。总而言之,考虑到像上面这样的复杂代码,也许将标志视为单个 uint8_t 并为其访问宏会更容易,例如

#define FLAG_N 0x80U
...
#define FLAG_C 0x1U

#define SET_FLAG(flag_var, flag) ((flag_var) |= (flag))
#define CLR_FLAG(flag_var, flag) ((flag_var) &= ~(flag))
#define GET_FLAG(flag_var, flag) ((_Bool)((flag_var) & (flag)))

uint8_t flags = 0;
SET_FLAG(flags, FLAG_C);

if (GET_FLAG(flags, FLAG_N)) { ... }

这样 PHP 指令可以编码为按原样推送 flags...

Except that the B flag that is pushed is not a flag from the status register...

与其将结构转换为 (uint8_t),不如将其地址转换为 (uint8_t *):

#include <stdio.h>

struct Flags {
    uint8_t C: 1;
    uint8_t Z: 1;
    uint8_t I: 1;
    uint8_t D: 1;
    uint8_t V: 1;
    uint8_t N: 1;
    uint8_t B: 2;
};

int main() {
    struct Flags f = {0, 0, 0, 0, 1, 0, 0};
    uint8_t f_as_byte = *(uint8_t *)&f;
    printf("flags: %.2X\n", f_as_byte);
    return 0;
}

但是请注意,用于表示位域成员的实际位值是实现定义的。 C 标准甚至不保证 Flags 结构适合单个字节。这使得位域成为表示硬件寄存器或指令操作码的冒险选择,因为这会使仿真器不可移植。小端和大端系统通常对位域成员使用不同的布局。