将一个结构放入比它小的内存中

Ann*_*inn 5 c++ optimization strict-aliasing language-lawyer bit-fields

我正在优化一种压缩算法,该算法使用跨 2 个字节的结构。但有时我希望它只解释 1 个字节,因为(我希望)映射到第二个字节的成员永远不会被写入或读取。

我是否能保证编译器不会访问第二个字节,只要 和zFmt永远wFmt不会被访问?如果不是,我可以编写一个静态断言,当这个假设错误时将停止编译吗?

struct Header {
    uint8_t xFmt : 4;
    uint8_t yFmt : 4;
    uint8_t zFmt : 4; // must not be read/written when header is mapped to 1 byte
    uint8_t wFmt : 4; // must not be read/written when header is mapped to 1 byte
};
    
static_assert( sizeof(Header) == 2 && alignof(Header) == 1, "alignment vital");


// --- usage ---
int main(){
    // Header may be placed into memory where it overlaps only one byte;
    // in that case, it's .zFmt and .wFmt members are never read or written to
    char buffer[1];
    Header * header = new (buffer) Header;

    // can I be sure (or statically assert) that these instructions
    // will only read and write to the nearest (and only) owned byte?
    header->xFmt = 0;
    header->yFmt = 0;
    header->xFmt += 1; 
    header->yFmt += 1; 
}
Run Code Online (Sandbox Code Playgroud)

旁注:

该算法目前有效,但我想确保它不依赖于未定义的行为。我相信通过使用新的放置来遵守严格别名,但也许这个假设是不正确的?

另外,我想以这种方式使用这个结构和位字段......因为它们看起来不错!不是最好的原因哈哈,所以如果这是不可能的,我的后备方法是解释没有结构的字节,就像uint8_t移位和掩码一样。我还知道我可以使用继承来执行切片,如果这是未定义的,我将研究它。

Ann*_*inn 1

回答我自己的问题,因为我相信我已经在 2020 版 C++ ISO 标准中找到了答案(将相关部分加粗):

\n
\n
    \n
  • 内存位置可以是标量类型的对象,也可以是具有非零宽度的相邻位字段的最大序列。[注意:语言的各种功能,例如引用和虚拟函数,可能涉及\n程序无法访问但由实现管理的其他内存位置。\n\xe2\x80\x94 尾注] 两个或多个线程执行(6.9.2)可以访问单独的内存位置,而不会互相干扰。
  • \n
\n
\n
\n
    \n
  • [注意:因此,位字段和相邻的非位字段位于不同的内存位置,因此可以由两个执行线程同时更新而不会产生干扰。这同样适用于两个位域,\n如果一个位域是在嵌套结构声明内声明的,而另一个不是,或者如果这两个位域由零长度位域声明分隔,或者如果它们由非-位域声明。如果同一结构中的两个位域之间的所有域都是非零宽度的位域,那么同时更新这两个位域是不安全的。\xe2\x80\x94 尾注]
  • \n
\n
\n
struct {\n    char a;\n    int b:5,\n    c:11,\n    :0,\n    d:8;\n    struct {int ee:8;} e;\n}\n
Run Code Online (Sandbox Code Playgroud)\n
\n
    \n
  • [示例:声明为[上述]的类包含四个单独的内存位置:成员 a 以及位字段 d 和 e.ee 都是单独的内存位置,并且可以同时修改而不会相互干扰。位字段 b 和 c\n 一起构成第四个存储位置。位字段 b 和 c 不能同时修改,但例如 b 和 a 可以。\xe2\x80\x94结束示例]
  • \n
\n
\n

关于“两个或多个执行线程”访问单独的内存位置的注释使我相信以下内容:

\n
    \n
  • 访问yFmt不会导致明显的副作用。正如 Nate 的评论所指出的,编译后的代码仍然可以加载并存储到它认为zFmt存在的位置,同时满足此要求。但是,如果访问必须是原子的并保留先前的值(只要程序拥有此内存),那么就只有一种可能的行为。(至于拥有内存的程序,只要原子指令在比程序可以拥有内存更精细地对齐的内存上工作,我相信情况就是如此,那么我很舒服地假设我不会导致访问冲突这边走。)
  • \n
\n

除了结构的所有成员和位字段必须具有递增地址的规则之外,我相信这使我的用法得到了明确的定义,并进行了以下更改:

\n
struct Header {\n    uint8_t xFmt : 4;\n    uint8_t yFmt : 4;\n    uint8_t :0;\n    uint8_t zFmt : 4; // must not be read/written when header is mapped to 1 byte\n    uint8_t wFmt : 4; // must not be read/written when header is mapped to 1 byte\n};\n\nstatic_assert( sizeof(Header) == 2 && alignof(Header) == 1, "");\n
Run Code Online (Sandbox Code Playgroud)\n

当然,并不是说它不容易导致程序员错误,但我相当确信这是明确定义的,至少目前是这样。

\n