Как реализовать memmove в стандартном C без промежуточной копии?

Из справочной страницы моей системы:

void * memmove (void * dst, const void * src, size_t len);

ОПИСАНИЕ
Функция memmove () копирует len байтов из строки src в строку dst.
Две строки могут перекрываться ; копирование всегда выполняется в неразрушающем
манера.

Из стандарта C99:

6.5.8.5 При сравнении двух указателей результат зависит от относительные местоположения в адресе пространство объектов, на которые указывает. Если два указателя на объект или неполный оба типа указывают на один и тот же объект, или оба указывают на один за последним элемент того же объекта массива, они равны. Если объекты указаны члены того же агрегатный объект, указатели на члены структуры объявлены позже сравнить больше указателей на члены, заявленные ранее в структура и указатели на массив элементы с большими значениями нижнего индекса сравнить больше указателей на элементы того же массива с меньшими значения нижнего индекса. Все указатели на члены одного и того же объекта объединения сравнить равные. Если выражение P указывает на элемент массива объект, а выражение Q указывает на последний элемент того же массива объект, выражение указателя Q + 1 сравнивает больше, чем P . В целом в других случаях поведение undefined .

Это мой акцент.

Аргументы dst и src могут быть преобразованы в указатели на char , поэтому чтобы облегчить проблемы строгого наложения имен, но можно ли сравнить два указателя, которые могут указывать внутри разных блоков, чтобы выполнить копирование в правильном порядке, если они указывают внутри одного блока?

Очевидное решение - ] if (src , но это не определено, если src и dst указывают на разные блоки. «Не определено» означает, что вам не следует даже предполагать, что условие возвращает 0 или 1 (это было бы названо «неопределенным» в словаре стандарта).

Альтернативой является if ((uintptr_t) src , который, по крайней мере, не указан, но я не уверен, что стандарт гарантирует, что когда определено src , это эквивалентно (uintptr_t) src . Сравнение указателя определяется арифметикой указателя. Например, когда я читаю раздел 6.5.6 о добавлении, мне кажется, что арифметика указателей может идти в направлении, противоположном арифметике uintptr_t , то есть, что может иметь совместимый компилятор, когда p имеет тип char * :

((uintptr_t)p)+1==((uintptr_t)(p-1)

Это только пример. Вообще говоря, при преобразовании указателей в целые числа гарантируется очень мало.

Это чисто академический вопрос, потому что memmove предоставляется вместе с компилятором. На практике авторы компилятора могут просто продвигать сравнение неопределенных указателей с неопределенным поведением, или используйте соответствующую прагму, чтобы заставить их компилятор правильно скомпилировать их memmove . Например, эта реализация имеет следующий фрагмент:

if ((uintptr_t)dst < (uintptr_t)src) {
            /*
             * As author/maintainer of libc, take advantage of the
             * fact that we know memcpy copies forwards.
             */
            return memcpy(dst, src, len);
    }

Я все же хотел бы использовать этот пример в качестве доказательства того, что стандарт заходит слишком далеко с неопределенным поведением, если это правда, что memmove не может быть эффективно реализован в стандарте C. Например, никто не поставил галочку при ответе на этот вопрос SO .

33
задан Community 23 May 2017 в 12:03
поделиться