/ Использует ли / memmove промежуточный буфер?

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

Копирование происходит как будто использовался промежуточный буфер

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

Если бы вы попросили меня написать memmoveЯ бы, вероятно, автоматически сделал следующее:

  • выделять n байтов в куче
  • memcpy источник температуры
  • memcpy температура до места назначения
  • освободить буфер

Я надеялся, что кто-нибудь сможет …

  1. … подтвердите, что формулировка правильна только потому, что пользователям проще визуализировать происходящее, не исправляя при этом конкретные реализации иметь использовать промежуточный буфер;
  2. … пролить свет на фактический реализация в некоторых распространенных компиляторах C ++ (например, gcc или Visual C ++) — например, делает он использует буфер и проверяет перекрытие, чтобы memcpy непосредственно;
  3. … Может быть, указать на очевидную ошибку / неэффективность в моем простом алгоритме выше.

1

Решение

  1. В самом деле. «Как будто» означает, что он должен вести себя так, как будто он это сделал; но не ограничивает реализацию на самом деле это сделать. Единственное обязательное поведение заключается в том, что целевой буфер заканчивается правильными байтами из исходного буфера, независимо от того, перекрываются ли буферы или нет.

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

  3. Там нет ошибок, если распределение не удается. Недостатками являются выделение и освобождение временного буфера и копирование каждого байта дважды, а не один раз.

4

Другие решения

Вы абсолютно правы в # 1 — описание призвано помочь пользователям визуально представить, что происходит логически, а не объяснять, как это реализовано.

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

Единственная проблема вашего алгоритма состоит в том, что он может запустить вашу систему из памяти, когда в этом нет необходимости: представьте, что ваша программа пытается переместить буфер размером 60% от разрешенной общей памяти, чтобы увидеть пример того, когда это произойдет.

3

Одна из первых возможностей оптимизации, которая приходит на ум, — это выполнить простой memcpy (), если буферы не перекрываются. Из-за плоской природы (виртуального) адресного пространства это легко проверить. Я посмотрел на Glibc а также Android реализации, и обе делают это (Android легче следовать непосвященным).

Выделение памяти в куче, вероятно, не требуется, потому что это будет довольно медленно (динамическое распределение не так уж дешево).

Если буферы перекрываются, мы могли бы оптимизировать копирование частей, которые этого не делают, а в остальном мы могли бы использовать небольшой «чистый» буфер, но это было бы выделено в стеке, если вообще требуется какое-либо распределение. Android один копирует только один байт за раз; мы можем добиться большего успеха на amd64, но это тот же тип оптимизации, который уже был бы выполнен в memcpy. Glibc one копирует либо вперед, либо назад, в зависимости от характера перекрытия (это то, к чему относится «BWD» в источнике).

2

Реализация не имеет значения. Формулировка должна гарантировать, что память будет правильно обрабатываться.

char buf[] = { 0x11, 0x22, 0x33, 0x00 };
memcpy(buf, buf + 1, 3);

может привести к buf являющийся { 0x11, 0x11, 0x11, 0x11 },

в то время как

char buf[] = { 0x11, 0x22, 0x33, 0x00 };
memmove(buf, buf + 1, 3);

гарантируется, что buf будет { 0x11, 0x11, 0x22, 0x33 },

1
По вопросам рекламы [email protected]