对齐简单的类以允许在没有UB的情况下访问数组 [英] Alignment of a simple class to allow array access without UB

查看:168
本文介绍了对齐简单的类以允许在没有UB的情况下访问数组的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我有以下简单的课程:

    struct  employee{
        std::string name;
        short salary;
        std::size_t age;
        employee(std::string name, short salary, std::size_t age) : name{name}, salary{salary}, age{age}{}
    };

由于我想要对一组雇员数组中的雇员的name成员进行类似数组的访问,因此我需要将偏移量整除:

    static_assert( sizeof(employee) % sizeof(std::string) == 0, "!" );

为了确保我正在使用alignas指令:

    struct alignas(sizeof(std::string)) employee{
        std::string name;
        short salary;
        std::size_t age;
        employee(std::string name, short salary, std::size_t age) : name{name}, salary{salary}, age{age}{}
    };

似乎可以完成这项工作(现在上面的static_assert通过了).

但是,当我打开clang UB(未定义的行为消毒剂)时,我尝试构造此clang类的对齐版本的数组,将检测到错误:

SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/move.h:139:31 in 
/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/ext/new_allocator.h:153:10: runtime error: member call on misaligned address 0x0000022de1b0 for type 'employee', which requires 32 byte alignment
0x0000022de1b0: note: pointer points here
 00 00 00 00  c0 e1 2d 02 00 00 00 00  05 00 00 00 00 00 00 00  44 61 76 69 64 00 00 00  00 00 00 00

然后允许雇员和name成员兼容的alignment的正确方法是什么?(因此可以通过指针std::string*算术访问数组的成员)

奖励问题:如何对齐所有成员以允许对employee s数组的所有成员进行数组访问.

有关更多详细信息,请参见此处:对齐/偏移结构的特定成员

基本上,我注意到根据clang的解决方案是UB,我正在寻找替代方案.


通过成员的数组访问,我的意思是能够做到这一点:

employee ARRAY[2];
std::string* p = &ARRAY[0].name;
std::string& p2 = *(p + 2); // can be +3 or any necessary integer
assert( &p2 == &ARRAY[1].name );


请注意,我发现这行得通(在我的系统中),该作业是否可以匹配跨步并且clang不会说是UB:

    struct employee{
        std::string name;
        short salary;
        std::size_t age;
        char dummy[9];
        employee() = default;
    }

这是我到目前为止发现的唯一不会产生UB的选项. 我想知道是否还有更好的方法.

最惯用的路径似乎使用alignas,但它也会根据clang触发UB.

    struct employee{
        std::string name alignas(32);
        short salary;
        std::size_t age;
        employee(std::string name, short salary, std::size_t age) : name{name}, salary{salary}, age{age}{}
    };

解决方案

因此,它非常hacky.但这行得通.

struct employee{
    std::string name;
    ...
};

employee ARRAY[2];
std::string* p = &ARRAY[0].name;
std::string* p2 = (std::string*)((char*)p + i * sizeof(employee));

这有效,只要您按sizeof(employee)递增即可.
您不能将其强制转换为& reference,因为编译器会选择该方法来实现取消引用,而这是通过指针算法无法实现的.

也许还有更多选择.这是非常不安全的...

Suppose I have the following simple class:

    struct  employee{
        std::string name;
        short salary;
        std::size_t age;
        employee(std::string name, short salary, std::size_t age) : name{name}, salary{salary}, age{age}{}
    };

Since I want array-like access to the name member of employee in an array of employees, I need for example that the offsets are divisible:

    static_assert( sizeof(employee) % sizeof(std::string) == 0, "!" );

In order to ensure that I am using the alignas directive:

    struct alignas(sizeof(std::string)) employee{
        std::string name;
        short salary;
        std::size_t age;
        employee(std::string name, short salary, std::size_t age) : name{name}, salary{salary}, age{age}{}
    };

Which seems to do the job (now the static_assert above passes).

However when I turned on the clang UB (undefined behavior sanitizer) and I try to construct an array of this aligned version of the class clang detects an error:

SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/move.h:139:31 in 
/usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/ext/new_allocator.h:153:10: runtime error: member call on misaligned address 0x0000022de1b0 for type 'employee', which requires 32 byte alignment
0x0000022de1b0: note: pointer points here
 00 00 00 00  c0 e1 2d 02 00 00 00 00  05 00 00 00 00 00 00 00  44 61 76 69 64 00 00 00  00 00 00 00

What would be then the correct way to allow a compatible alignment of employee and the name members? (so member of arrays can be accessed by pointer std::string* arithmetic)

BONUS question: How can all the members be aligned to allow array access on all members of an array of employees.

For more details see here: Align/offset specific members of a struct

Basically I am noting that the solutions that worked are UB according to clang and I am looking for alternatives.


By array access of members I mean being able to this:

employee ARRAY[2];
std::string* p = &ARRAY[0].name;
std::string& p2 = *(p + 2); // can be +3 or any necessary integer
assert( &p2 == &ARRAY[1].name );


Note that I found this worked (in my system), does the job wrt to matching strides and clang doesn't say is UB:

    struct employee{
        std::string name;
        short salary;
        std::size_t age;
        char dummy[9];
        employee() = default;
    }

This is the only option I found so far that doesn't produce UB. I wonder if there is a better way still.

The most idiomatic path seems to use alignas but it also triggers UB according to clang.

    struct employee{
        std::string name alignas(32);
        short salary;
        std::size_t age;
        employee(std::string name, short salary, std::size_t age) : name{name}, salary{salary}, age{age}{}
    };

解决方案

So, it is very hacky. But this works.

struct employee{
    std::string name;
    ...
};

employee ARRAY[2];
std::string* p = &ARRAY[0].name;
std::string* p2 = (std::string*)((char*)p + i * sizeof(employee));

This works, as long as you increment by sizeof(employee).
You can't cast it into a &reference since with those the compiler chooses how to implement the de-referencing, which you have made impossible by pointer arithmetic.

Maybe there are more options. This is quite unsafe...

这篇关于对齐简单的类以允许在没有UB的情况下访问数组的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆