【问题标题】:Setting part of an array to zero efficiently有效地将数组的一部分设置为零
【发布时间】:2020-10-07 11:08:09
【问题描述】:

我正在尝试将一组简单对象重置为零,如下所示:

using Node = std::array<std::uint16_t, 256>;
using Array = std::array<Node, 4096>;

void reset_1(Array& a) {
  std::fill(std::begin(a), std::end(a), Node{});
}
    
void reset_2(Array& a) {
  a = {};
}

void reset_3(Array& a) {
  a.fill(Node{});
}

所有这些函数都完成了工作,但是查看编译器资源管理器(同时使用 Clang 10.0.1 和 gcc 10.2),reset_2 在一个 memset 中完成,而另外两个在多个块中完成(使用std::fill_n 的结果相同)。

在我的真实案例中,我实际上想要重置所有节点,除了第一个节点。这将导致我写如下内容:

void reset_not_zero_1(Array& a) {
  std::fill(std::begin(a) + 1, std::end(a), Node{});
}
    
void reset_not_zero_2(Array& a) {
  std::memset(a.data() + 1, 0, sizeof(a) - sizeof(Node));
}

我的问题是:我是否使用reset_not_zero_2 调用了某种未定义的行为?该数组可以简单地复制,但也许我遗漏了一些东西。

【问题讨论】:

  • 我也会考虑这个选项:将第一个元素存储在temp,重置整个数组,将temp分配给第一个元素。
  • 没关系。 std::array 只是一个包裹在对象中的 c 样式数组,用于向其中添加所有复制语义和其他方便的东西。

标签: c++ arrays memset


【解决方案1】:

来自cppreference

将值 ch 转换为 unsigned char 并将其复制到每个 dest 指向的对象的第一个 count 字符。如果对象 是可能重叠的子对象或不是 TriviallyCopyable (例如,标量、C 兼容的结构或可简单复制的数组 类型),行为未定义。如果计数大于 dest指向的对象,行为未定义。

到目前为止一切顺利。

假设另一个节点大小是 14,但它的 alignas 是 16

using Array = std::array<Node, 4096>;

'a' 的大小将是 16*4096 = 65536,因为 Node 会强制填充 2。

std::memset(a.data() + 1, 0, sizeof(a) - sizeof(Node));

这将是 65536-16 个字节中的 65536-14 个,这是不行的,因为它会覆盖下一个数据的 2 个字节。

如果另一方面

sizeof(a) == a.size()*sizeof(a::value_type) 

那应该没问题。

另外,如果 alignas 强制使用更大的 sizeof 也应该没问题。

【讨论】:

  • sizeof 始终包含填充。
  • @eerorika 不只是用于内部填充吗?与 alignas 强制填充相反。
  • 如果 Node 的对齐方式是 16,那么 Node 数组的两个元素的开头相距 16 个字节。这个距离定义为 sizeof 返回的值。
  • @eerorika 我没有看到 sizeof 对en.cppreference.com/w/cpp/language/object#Alignment 的对齐有任何影响,但如果比成员更严格,我的编译器会返回 sizeof 作为 alignas。
【解决方案2】:

我的问题是:我是否使用 reset_not_zero_2 调用了某种未定义的行为?

没有。我觉得很好。

【讨论】:

    猜你喜欢
    • 2016-09-12
    • 1970-01-01
    • 1970-01-01
    • 2017-04-26
    • 2012-01-28
    • 2016-06-22
    • 2015-11-16
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多