Если пункт назначения и источник совпадают, что делает memmove?

Если пункт назначения и источник совпадают, memmove все еще "перемещать" данные (или они возвращаются напрямую)? Как насчет realloc; Что делать, если новый размер совпадает со старым размером?

3 ответа

Решение

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

Это будет работать в любом случае, но, предположительно, подходящая умная реализация будет проверять наличие перекрывающихся сегментов (и особенно в случае, когда source == dest) и справиться с этим соответствующим образом.

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

Лучше не передавать неверные указатели в надежде, что они не получат доступ к данным;-)

Как уже говорили другие, спецификация не требует ярлыка, и неясно, когда добавление дополнительной ветки действительно улучшает производительность.

Но это не отвечает на вопрос, что на самом деле происходит, когда вы звоните memmove. Я покопался в исходном коде glibc и нашел множество реализаций сборки для различных архитектур, а также переносимую реализацию C.

TL;DR заключается в том, что чистая версия C не имеет ярлыков, а версия сборки x86_64 (я думаю).

Версия на чистом C - это довольно стандартный цикл memmove. Единственный трюк заключается в том, что если вы перемещаете 16 КиБ или более, он будет управлять виртуальной памятью вместо копирования байтов. Функция определена в строке / memmove.c, а суть реализации - этоBYTE_COPY_FWDмакрос из sysdeps / generic / memcopy.h.

Для сборки x86_64 существует несколько версий в зависимости от доступных инструкций (например, AVX, SSE и т. Д.). Они находятся в sysdeps / x86_64 / multiarch.

Вот одна реализация из memmove-vec-unaligned-erms.S, в которой используется Enhanced REP MOVSB ​​(ERMS):

ENTRY (__memmove_erms)
        movq    %rdi, %rax
        /* Skip zero length.  */
        test    %RDX_LP, %RDX_LP
        jz      2f
L(start_movsb):
        mov     %RDX_LP, %RCX_LP
        cmp     %RSI_LP, %RDI_LP
        jb      1f
        /* Source == destination is less common.  */
        je      2f
        lea     (%rsi,%rcx), %RDX_LP
        cmp     %RDX_LP, %RDI_LP
        jb      L(movsb_backward)
1:
        rep movsb
2:
        ret
L(movsb_backward):
        leaq    -1(%rdi,%rcx), %rdi
        leaq    -1(%rsi,%rcx), %rsi
        std
        rep movsb
        cld
        ret
END (__memmove_erms)

Моя способность читать сборку невелика, но, насколько я могу судить, эта реализация сокращает случай, когда источник и место назначения совпадают.

Если я правильно прочитал, все начнется со сравнения указателей. Если пункт назначения предшествует источнику, он переходит к метке 1 (jb 1f) который вызывает rep movsb. Насколько я понимаю, это в основном инструкция поmemcpy. Если указатели равны, выполняется переход к метке 2 (je 2f), который немедленно возвращается. В противном случае он организуетrep movsb для обратного просмотра данных.

Я также взглянул на реализацию SSSE3 в sysdeps / x86_64 / multiarch / memcpy-ssse3.S. Эта версия также, похоже, реализует ярлык.

Очевидно, все это применимо только к glibc. Я не проверял llvm-libc.

По крайней мере для realloc, неявно предполагается, что условие "нет необходимости в перемещении" существует и является действительным, поскольку перемещение отмечается как особый случай:

Функция realloc() должна изменить размер объекта памяти, на который указывает ptr, на размер, указанный в size. Содержимое объекта должно оставаться неизменным вплоть до меньшего из новых и старых размеров. Если новый размер объекта памяти потребует перемещения объекта, пространство для предыдущего создания объекта освобождается.

Формулировка "если... будет" предполагает, что это не всегда так. Конечно, для реализации вообще не требуется опускать ненужную копию.

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

Итак, одним словом: не указано.

Другие вопросы по тегам