Безопасно ли копирование memmove 0 байт, но ссылка за пределы

#c #memmove

#c #memmove

Вопрос:

Я прочитал в Интернете, что 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(
            amp;arr[index],
            amp;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;
}
  

Когда Len есть 10 , depth есть 10 , а index 9 затем index 1 будет считываться за пределы. Однако и в этом случае depth - index - 1 0 это должно означать memmove , что не будет выполнять никаких действий. Безопасен ли этот код или нет?

Комментарии:

1. Даже если это безопасно (как объяснил @paxdiablo в своем ответе), это неверно: ничего не будет перемещено, но depth будет уменьшено.

2. @VladFeinstein, нет, это правильно. Когда последний элемент удаляется, ничего не нужно сдвигать (нет memmove ), но количество элементов должно уменьшаться ( depth-- )

Ответ №1:

memmove Функция скопирует n байты. Если n равно нулю, он ничего не сделает.

Единственная возможная проблема связана с этим, где index уже установлено максимальное значение для элементов массива:

 amp;arr[index   1]
  

Однако вам разрешено ссылаться на элементы массива (с точки зрения указателя на них) внутри массива или гипотетического элемента сразу за концом массива.

Вы не можете разыменовывать последнее, но здесь вы этого не делаете. Другими словами, хотя arr[index 1] само по себе оно будет пытаться выполнить разыменование и, следовательно, будет недействительным, оценка его адреса в порядке.

Это рассматривается, хотя и косвенно, в C 20 [expr.add] :

Когда выражение, имеющее целочисленный тип, добавляется к указателю или вычитается из него, результат имеет тип операнда указателя. Если выражение P указывает на элемент x[i] объекта массива x с n элементами, выражения P J и J P (где J имеет значение j ) указывают на (возможно-гипотетический) элемент x[i j] if 0 ≤ i j ≤ n ; в противном случае поведение не определено.

Обратите внимание на if 0 ≤ i j ≤ n предложение, особенно на последнее . Для массива int x[10] выражение amp;(x[10]) допустимо.

Это также описано в [basic.compound] (мой акцент):

Значение типа указателя, которое является указателем на конец объекта или после него, представляет адрес первого байта в памяти, занятой объектом, или первого байта в памяти после окончания хранилища, занятого объектом, соответственно.

Комментарии:

1. Можно ли также предположить memmove , что он не будет читать arr[index 1] ?

2. @asimes: для этого чтения нет причин : из раздела ISO C, на который ссылается ссылка: » memmove функция копирует n символы из объекта, на который указывает by s2 , в объект, на который указывает by s1 «. В стандарте нет конкретной гарантии , что этого не произойдет, но это ничем не отличается от утверждения, что нет гарантии, что он не попытается разыменовать постоянный указатель 0xdeadbeef 🙂 Если он это сделает , вы можете предположить, что он знает, что это безопасно. Я думаю, разумно предположить, что библиотечные функции будут делать то, что они утверждают, что они делают, и не более.

3. @asimes memmove() может читать arr[index 1] — ему может потребоваться читать 8 байт за раз. Он может читать arr[index - 42] . Он может безопасно выполнять действия, недоступные пользовательскому коду. Почему вас волнует, читает ли он arr[index 1] ?

4. @asimes: UB определяет, что разрешено делать вашему коду, код в реализации этим не ограничен. Например, если он всегда выдает вам массивы, округленные до кратного восьми байтам, он знает, что безопасно копировать их частями по восемь и может действовать соответствующим образом.

5. По вышеуказанной причине попытка научиться программировать, изучая реализацию стандартной библиотеки, может привести вас ко многим ошибочным путям. Люди, пишущие библиотеку, точно знают, как в противном случае сумасшедший код batsmurf будет вести себя со своим компилятором на целевом оборудовании, и чтобы получить все до последней капли производительности из кода, они БУДУТ использовать ярлыки через неопределенную территорию. Когда вы действительно знаете правила, вы знаете, когда вы можете безопасно их нарушать.