【问题标题】:Is memmove copying 0 bytes but referencing out of bounds safememmove 是否复制 0 个字节但引用越界安全
【发布时间】:2021-02-25 00:30:28
【问题描述】:

我在网上读到memmove 如果要复制的字节数为 0,则预计不会执行任何操作。但是我想知道的是,是否预计不会读取源和目标指针案例

下面是我的部分代码的简化版,我感兴趣的部分是shiftLeft

#include <array>
#include <cstring>
#include <iostream>

class Foo final {
    unsigned just       = 0;
    unsigned some       = 0;
    unsigned primitives = 0;
};

template <unsigned Len>
class Bar final {
    unsigned             depth = 0;
    std::array<Foo, Len> arr;

public:
    Bar() = default;

    // Just an example
    void addFoo() {
        arr[depth] = Foo();
        depth++;
    }

    void shiftLeft(unsigned index) {
        // This is what my question focuses on
        // If depth is 10 and index is 9 then index + 1 is out of bounds
        // However depth - index - 1 would be 0 then
        std::memmove(
            &arr[index],
            &arr[index + 1],
            (depth - index - 1) * sizeof(Foo)
        );
        depth--;
    }
};

int main() {
    Bar<10> bar;
    for (unsigned i = 0; i < 10; ++i)
        bar.addFoo();
    bar.shiftLeft(9);
    return 0;
}

Len10depth10,并且index9 时,index + 1 会读出越界。然而,在这种情况下,depth - index - 1 也是0,这意味着memmove 不会执行任何操作。这段代码安全吗?

【问题讨论】:

  • 即使它是安全的(正如@paxdiablo 在他的回答中解释的那样),它也不正确:不会移动任何内容,但会减少 depth
  • @VladFeinstein,不,它是正确的。当最后一个元素被删除时,不需要移动任何东西(没有memmove)但元素的数量需要减少(depth--

标签: c++ memmove


【解决方案1】:

memmove 函数将复制 n 字节。如果n 为零,它什么也不做。

唯一可能的问题是,index 已经是数组元素的最大值:

&arr[index + 1]

但是,您可以引用数组中的数组元素(根据指向它们的指针)或数组末尾之外的假设元素。

您可能不会取消引用后者,但您在这里没有这样做。换句话说,虽然 arr[index + 1] 本身会尝试取消引用,因此无效,但评估它的 地址 就可以了。

C++20 [expr.add]:

当一个整数类型的表达式被添加到指针或从指针中减去时,结果具有指针操作数的类型。如果表达式P 指向带有n 元素的数组对象x 的元素x[i],则表达式P + JJ + P(其中J 的值为j)指向(可能是假设的)元素x[i + j] if 0 ≤ i + j ≤ n;否则,行为未定义。

注意if 0 ≤ i + j ≤ n 子句,尤其是最后的。对于数组int x[10],表达式&amp;(x[10]) 是有效的。

[basic.compound](我的重点)也涵盖了它:

指针类型的值是指向或超过对象末尾的指针表示该对象在内存中占用的第一个字节的地址或对象占用的存储空间结束后内存中的第一个字节,

【讨论】:

  • 假设memmove 不会读取arr[index + 1] 是否也安全?
  • @asimes:没有 reason 发生这种读取:来自引用的 ISO C 部分:“memmove 函数从指向的对象复制 n 字符通过s2s1 指向的对象中。标准中没有具体的 保证 它不会,但这与说它不会尝试取消引用常量指针 0xdeadbeef 没有什么不同:-) 如果它 确实 这样做,你可以假设它知道这样做是安全的。我认为可以合理地假设库函数会按照它们所说的那样做,仅此而已。
  • @asimes memmove() 可能会读取 arr[index + 1] - 它可能希望一次读取 8 个字节。它可能是arr[index - 42]。它可以安全地做用户代码做不到的事情。你为什么关心它是否写成arr[index + 1]
  • @asimes:UB 规定了你的代码可以做什么,实现中的代码不受此限制。例如,如果它总是为您提供四舍五入到八字节的倍数的数组,它知道以八字节为一组复制它们是安全的,并且可以采取相应的行动。
  • 由于上述原因,试图通过学习标准库实现来学习编程可能会导致您走上许多错误的道路。编写库的人确切地知道,否则 batsmurf 疯狂的代码将如何与他们的编译器在目标硬件上运行,并从代码中获得最后一滴性能,他们将通过未定义的领域采取捷径。当您真正了解规则时,您就知道何时可以安全地打破它们。
猜你喜欢
  • 1970-01-01
  • 2021-07-13
  • 2017-08-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多