c++ 使用struct分块处理数组,然后转换为平面数组-如何避免UB(严格别名)?

eh57zj3b  于 2022-12-20  发布在  其他
关注(0)|答案(2)|浏览(103)

外部API需要一个指向值数组的指针(这里的简单示例是int)加上一个大小。
以4个元素为一组处理这些元素在逻辑上更清楚。
因此,通过一个“4个一组”结构体处理元素,然后使用指针强制转换将这些结构体的数组传递给外部API。
蜘蛛感说:reinterpret_cast =〉可能UB中的“严格混叠违规”?
1.以下static_asserts是否足以确保:a)这在实践中有效B)这实际上符合标准,而不是UB?
1.否则,我需要做什么,让它“不是UB”。工会?请具体怎么做?
1.或者,总体上有没有一种不同的,更好的方式?

#include <cstddef>

void f(int*, std::size_t) {
    // external implementation
    // process array
}

int main() {

    static constexpr std::size_t group_size    = 4;
    static constexpr std::size_t number_groups = 10;
    static constexpr std::size_t total_number  = group_size * number_groups;

    static_assert(total_number % group_size == 0);

    int vals[total_number]{};

    struct quad {
        int val[group_size]{};
    };

    quad vals2[number_groups]{};
    // deal with values in groups of four using member functions of `quad`

    static_assert(alignof(int) == alignof(quad));
    static_assert(group_size * sizeof(int) == sizeof(quad));
    static_assert(sizeof(vals) == sizeof(vals2));

    f(vals, total_number);
    f(reinterpret_cast<int*>(vals2), total_number); /// is this UB? or OK under above asserts?
}
8iwquhpp

8iwquhpp1#

不,这是不允许的。相关的C++标准节是§7.6.1.10。从第一段开始,我们有(强调我的)
表达式reinterpret_­cast<T>(v)的结果是将表达式v转换为类型T的结果,如果T是对函数类型的左值引用类型或右值引用,则结果为左值;如果T是对对象类型的右值引用,则结果是x值;否则,结果是一个纯右值,并且对表达式v执行左值到右值、数组到指针和函数到指针的标准转换。下面列出了可以显式使用reinterpret_Çcast执行的转换。没有其他转换可以显式使用reinterpret_Çcast执行。
因此,除非你的用例列在特定的页面上,否则它是无效的。大多数部分与你的用例无关,但这是最接近的一个。
一个对象指针可以显式地转换成不同类型的对象指针。[58]当对象指针类型的纯右值v被转换成对象指针类型“指针到cv T“时,结果是static_­cast<cv T*>(static_­cast<cv void*>(v))
所以reinterpret_cast从一个指针类型到另一个指针类型等价于static_cast通过一个适当的cv限定的void*,现在,如果类型TS是 * 指针可互换的 *,那么从T*S*static_cast可以被接受地用作S*
两个对象a和b是指针可互换的,如果:

  • 它们是同一个对象,或者
  • 一个是联合对象,另一个是该对象的非静态数据成员([class.union]),或者
  • 一个是标准布局类对象,另一个是该对象的第一个非静态数据成员或该对象的任何基类子对象([class.mem]),或者
  • 存在对象C,使得A和C是指针可互换的,并且C和B是指针可互换的。

如果两个对象是指针可互换的,那么它们有相同的地址,并且可以通过reinterpret_Çcast([expr.reinterpret.cast])从指向一个对象的指针获得指向另一个对象的指针。
[Note 4:数组对象和它的第一个元素不能指针互换,即使它们有相同的地址。
总而言之,如果没有vtable可以阻止你,你可以将指向类C的指针强制转换成指向它的第一个成员的指针(然后返回);你可以将指向C的指针强制转换成指向C的另一个指针(如果你添加了cv限定符;例如,如果my_c_ptrC*,则reinterpret_cast<const C*>(my_c_ptr)是有效的)。对于联合,还有一些特殊的规则,这些规则在这里不适用。* 然而 ,您不能通过数组进行因子分解,如注解4所述。您 * 想要 * 的转换在这里是quad[] -> quad -> int -> int[],你不能在quad[]quad之间转换,如果quad是一个简单的结构体,只包含一个int,那么你可以把quad*重新解释为int*,但是你不能通过数组来实现,当然也不能通过它们的嵌套层。
我引用的章节中没有一个提到对齐、大小、压缩或填充,这些都不重要。你的static_assert所做的只是稍微增加未定义行为(
仍然 * 未定义)在更多编译器上发生作用的可能性。但是你是在用绷带修补大坝;这是行不通的。

jaxagkaj

jaxagkaj2#

再多的static_assert s也不能使某个 * 绝对 * UB的东西变成符合标准的定义良好的行为。你创建了一个包含int数组的结构体,这就是你所拥有的。
将指向quad的指针转换为指向int[group_size]的指针是合法的(尽管需要适当地修改代码,或者直接访问数组并将其转换为int*)。
不管你如何得到指向第一个元素的指针,在数组中进行指针运算都是合法的,但是当你试图在quad * 对象中进行指针运算超出数组边界的时候,你就得到了未定义的行为,指针运算是基于数组的存在而定义的:[表达式添加]/4
当将具有整数类型的表达式J添加到指针类型的表达式P中或从中减去时,结果具有P的类型。

  • 如果P的计算结果为空指针值,J的计算结果为0,则结果为空指针值。
  • 否则,如果P指向一个有n个元素的数组对象x的数组元素i([dcl. array]),表达式P + J和J + P(其中J的值为j)在0 ≤ i + j ≤ n时指向x的(可能假设的)数组元素i + j,表达式P-J在0 ≤ i − j ≤ n时指向x的(可能假设的)数组元素i − j。
  • 否则,行为未定义。

指针不为空,所以第一种情况不适用,上面的ngroup_size(因为数组是quad中的一个),所以如果索引〉group_size,那么第二种情况不适用。
因此,每当有人试图访问索引4之后的数组时,就会发生未定义的行为。没有任何强制转换可以覆盖这一行为。
否则,我需要做什么,让它"不是UB"。工会?请具体怎么做?
你不需要。你试图做的事情对于C++对象模型是无效的。你需要一个int的数组,所以你必须创建一个int的数组。你不能把int以外的数组当作int的数组(当然,字节数组是个小例外,但这对你没有帮助)。
分组处理数组的最简单 * 有效 * 的方法是......执行一些嵌套循环:

int arr[total_number];
for(int* curr = arr; curr != std::end(arr); curr += 4)
{
  //Use `curr[0]` to `curr[3]`;
  //Or create a `std::span<int, 4> group(curr)`;
}

相关问题