c++memmove

Is memmove copying 0 bytes but referencing out of bounds safe


I have read online that memmove is expected to perform no action if the number of bytes to copy is 0. However what I want to know is if it is expected that the source and destination pointers will not be read in that case

Below is a simplified version of some of my code, the section I am interested in is 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;
}

When Len is 10, depth is 10, and index is 9 then index + 1 would read out of bounds. However also in that case depth - index - 1 is 0 which should mean memmove would perform no action. Is this code safe or not?


Solution

  • The memmove function will copy n bytes. If n is zero, it will do nothing.

    The only possible issue is with this, where index is already at the maximum value for array elements:

    &arr[index + 1]
    

    However, you are permitted to refer to array elements (in terms of having a pointer point to them) within the array or the hypothetical element just beyond the end of the array.

    You may not dereference the latter but you're not doing that here. In other words, while arr[index + 1] on its own would attempt a dereference and therefore be invalid, evaluating the address of it is fine.

    This is covered, albeit tangentially, in C++20 [expr.add]:

    When an expression that has integral type is added to or subtracted from a pointer, the result has the type of the pointer operand. If the expression P points to element x[i] of an array object x with n elements, the expressions P + J and J + P (where J has the value j) point to the (possibly-hypothetical) element x[i + j] if 0 ≤ i + j ≤ n; otherwise, the behavior is undefined.

    Note the if 0 ≤ i + j ≤ n clause, particularly the final . For an array int x[10], the expression &(x[10]) is valid.

    It's also covered in [basic.compound] (my emphasis):

    A value of a pointer type that is a pointer to or past the end of an object represents the address of the first byte in memory occupied by the object or the first byte in memory after the end of the storage occupied by the object, respectively.