繁体   English   中英

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

[英]process array in chunks using struct then cast as flat array - how to avoid UB (strict aliasing)?

外部 API 需要一个指向值数组(此处以 int 作为简单示例)加上大小的指针。

以 4 为一组处理元素在逻辑上更清晰。

因此,通过“4 组”结构处理元素,然后使用指针转换将这些结构的数组传递给外部 API。 请参阅下面的代码。

Spider sense 说: reinterpret_cast中的“严格违反别名”=> 可能是 UB?

  1. 下面的static_asserts是否足以确保:a) 这在实践中有效 b) 这实际上符合标准而不是 UB?

  2. 否则,我需要做什么才能使其“不是 UB”。 工会? 请问具体如何?

  3. 或者,总体上有不同的更好的方法吗?


#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?
}

再多的static_assert也无法将绝对UB 的东西变成符合标准的明确定义的行为。 您没有创建int数组; 您创建了一个包含int数组的结构。 这就是你所拥有的。

将指向quad的指针转换为指向int[group_size]的指针是合法的(尽管您需要适当地更改代码。或者您可以直接访问数组并将其转换为int*

无论您如何获得指向第一个元素的指针,在该数组中进行指针运算都是合法的。 但是,您尝试在该quad对象内进行超出数组边界的指针运算时,您将获得未定义的行为。 指针运算是基于数组的存在来定义的: [expr.add]/4

当整数类型的表达式 J 与指针类型的表达式 P 相加或相减时,结果的类型为 P。

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

指针不为空,因此情况 1 不适用。 上面的ngroup_size (因为数组是quad中的一个),所以如果索引 > group_size ,那么情况 2 不适用。

因此,每当有人试图访问索引 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)`;
}

不,这是不允许的。 相关的 C++ 标准部分是§7.6.1.10 从第一段开始,我们有(强调我的)

表达式reinterpret_cast<T>(v)的结果是将表达式v转换为类型T的结果。 如果T是左值引用类型或函数类型的右值引用,则结果为左值; 如果T是对对象类型的右值引用,则结果是一个 xvalue; 否则,结果为纯右值,并且对表达式v执行左值到右值、数组到指针和函数到指针的标准转换。 下面列出了可以使用 reinterpret_cast 显式执行的转换。 不能使用 reinterpret_cast 显式执行其他转换。

因此,除非您的用例列在该特定页面上,否则它是无效的。 大多数部分与您的用例无关,但这是最接近的部分。

对象指针可以显式转换为不同类型的对象指针。 [58] 当对象指针类型的纯右值v转换为对象指针类型“指向cv T的指针”时,结果为static_cast<cv T*>(static_cast<cv void*>(v))

因此,从一种指针类型到另一种指针类型的reinterpret_cast等效于通过适当的 cv 限定的void*进行的static_cast 现在,如果类型TSpointer-interconvertible ,那么从T*S*static_cast可以被用作S* §6.8.4

如果满足以下条件,则两个对象 a 和 b 是指针可相互转换的:

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

如果两个对象是指针可相互转换的,则它们具有相同的地址,并且可以通过 reinterpret_cast ([expr.reinterpret.cast]) 从指向另一个的指针获得指向一个的指针。

[注意 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所做的只是略微增加了未定义行为(仍然未定义)发生在更多编译器上的可能性。 但是您正在使用创可贴来修复水坝; 这是行不通的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM