Пожалуйста, обратите внимание на это необъяснимое поведение и вывод memcpy() для перекрывающихся блоков памяти.

Прочитав следующее о memcpy(), я продолжил читать о memmove():

To avoid overflows, the size of the arrays pointed by both the destination and source parameters, shall be at least num bytes, and should not overlap (for overlapping memory blocks, memmove is a safer approach).(ССЫЛКА)

И после проверки программы, используемой для иллюстрации работы memmove(), я решил настроить ее, используя вместо этого memcpy(), чтобы увидеть, насколько отличается вывод. К моему удивлению, они одинаковы, даже если это случай перекрывающиеся блоки памяти. Вот программа и вывод, и я приступил к описанию моей путаницы после этого:

#include <stdio.h>
#include <string.h>

int main ()
{
  char str[] = "memmove can be very useful......";
  //memmove (str+20,str+15,11);
  memcpy(str+20,str+15,11);  //Simply used memcpy instead of memmove
  puts (str);
  return 0;
}

Вывод memmove can be very very useful.

Этот вывод такой же, как и для memmove(). И вот что меня смущает:

1) Почему вывод одинаков для обоих? Поскольку в случае memcpy() не используется промежуточный буфер, я ожидаю, что копирование начнется с копирования символа в позиции str+15 в позицию str+20 (перезапись того, что там есть). ), символ в позиции str+16 в позицию str+21 и так далее до символа в позиции str+20, который к настоящему времени изменился на символ в позиции str+15, который будет скопирован в позицию str+25.Но это не так, перезаписи нет и это действует так, как будто для записи точной исходной строки используется промежуточный буфер. Вот иллюстрация:

memmove can be very useful......  //Original positions before memcopy
               ^    ^
            str+15  str+20

memmove can be very vseful......
                    ^ copies str+15 to str+20

memmove can be very veeful......
                     ^ copies str+16 to str+21
memmove can be very verful......
                      ^ copies str+17 to str+22
memmove can be very veryul......
                       ^copies str+18 to str+23
memmove can be very very l......
                        ^ copies str+19 to str+24
memmove can be very very v......
                         ^ I expect 'v' to be copied from str+20 to str+25
                           as str+20  now has 'v',not 'u'
memmove can be very very ve.....
                          ^ I expect 'e' to be copied from str+21 to str+26 
                            as str+21 now has 'e' not 's'

Тогда почему memcpy() копирует его, поскольку memmove может быть очень-очень полезным вместо memmove может быть очень-очень v?

2) Теперь небольшой второстепенный вопрос, вытекающий из этого. О memmove() говорится следующее (ССЫЛКА)

Copying takes place as if an intermediate buffer were used, allowing the destination and source to overlap.

Что именно здесь as if? Разве промежуточный буфер на самом деле не используется для memmove()?


person Rüppell's Vulture    schedule 14.05.2013    source источник
comment
@Koushik Не undefined. Если не используется промежуточный буфер, я действительно ожидаю, что символ в str+20 будет перезаписан.   -  person Rüppell's Vulture    schedule 14.05.2013
comment
Если не будет промежуточного буфера, это будет определено реализацией. действительно не могу сказать.   -  person Koushik Shetty    schedule 14.05.2013
comment
@Koushik Поскольку второй параметр memcpy является константой, почему нам даже разрешено писать на него, не создавая ошибок для memcpy()?   -  person Rüppell's Vulture    schedule 14.05.2013
comment
компилятор не может узнать, к какой памяти вы обращаетесь. он имеет только указатель на источник. вы говорите, что источник не будет изменен путем обозначения второго параметра как константы, но вы нарушаете обещание, после чего компилятор не может помочь. Поэтому делать что-либо с объявленным константным местоположением является UB (даже если константное местоположение не только для чтения)   -  person Koushik Shetty    schedule 14.05.2013
comment
вы можете сделать это const int i = 10; ......int *ptr = &i;..*ptr = 100;..printf("%d",i). что такое УБ. но я нарушил завет. Компилятор предупреждает, но я могу это сделать. вы даже не можете быть уверены, что он напечатает   -  person Koushik Shetty    schedule 14.05.2013
comment
Рассматривали ли вы результат, когда запускаете эту программу на codepad?   -  person autistic    schedule 14.05.2013
comment
@undefinedbehaviour Как насчет этого? Что вы нашли? Расскажите, пожалуйста.   -  person Rüppell's Vulture    schedule 14.05.2013
comment
@Rüppell'sVulture Щелкните ссылку ... и я предостерег от использования cplusplus в качестве ссылки на C. Google opengroup memcpy и opengroup memmove.   -  person autistic    schedule 14.05.2013
comment
@undefinedbehaviour Боже, никогда бы не подумал, что Cplusplusreference однажды предаст меня.... Кому доверять?   -  person Rüppell's Vulture    schedule 14.05.2013
comment
@undefinedbehaviour LOL.. Ваша ссылка на кодовую панель дала такое глубокое понимание. Это так даже в Ideone ideone.com/kd1GpH .Эх, даже кодовые блоки предали меня. Это дало сбивающий с толку результат, а не ожидаемый результат. Как исправить кодовые блоки, чтобы они вели себя таким образом, как кодовая панель или идеон?   -  person Rüppell's Vulture    schedule 14.05.2013
comment
@Rüppell'sVulture Не надо. Очевидно, что ожидаемое вами поведение не является переносимым. Ищите что-то более портативное, чтобы у вас были бесконечные гарантии того, что все будет хорошо работать в будущем.   -  person autistic    schedule 14.05.2013


Ответы (1)


Если объекты перекрываются, поведение memcpy не определено. Нет смысла пытаться рассуждать о неопределенном поведении. Поскольку оно не определено, оно не поддается разуму. Вы знаете правила, и они четко задокументированы. Если объекты перекрываются, используйте memmove.

Что касается использования «как если бы», то есть для указания поведения, но без каких-либо ограничений на реализация. Это позволяет разработчику библиотеки использовать любой метод, который он считает нужным, при условии, что конечный результат будет таким же, как при использовании промежуточного буфера. Например, реализация может обнаружить, что объекты не перекрываются, и поэтому не использовать промежуточный буфер по соображениям производительности.

person David Heffernan    schedule 14.05.2013
comment
Прототипом memcpy() является void * memcpy ( void * destination, const void * source, size_t num ). т.е. источник должен быть константой. Почему тогда memcpy() позволяет изменять/записывать исходную строку? - person Rüppell's Vulture; 14.05.2013
comment
@Rüppell'sVulture, в документации четко сказано, что она не проверяет перекрытия, поэтому вам не следует передавать перекрывающиеся буферы, поэтому константа имеет смысл. - person perreal; 14.05.2013
comment
@perreal Почему нас не предупреждают и не выдают ошибку о том, что мы пытаемся изменить значение константы? - person Rüppell's Vulture; 14.05.2013
comment
Это UB для перекрытия источника и назначения. И ваша обязанность — соблюдать контракт и не передавать пересекающиеся объекты. Одной из целей стандартной библиотеки C является предоставление высокопроизводительного кода. И в данном случае это достигается за счет того, что программист гарантирует предварительные условия, а не библиотечная функция тратит время на проверку предварительных условий. - person David Heffernan; 14.05.2013
comment
@Rüppell'sVulture, поскольку вы передаете указатели, он не может предупредить вас, не проверив их в Интернете, поэтому он не определен. - person perreal; 14.05.2013
comment
Потому что это не то, что означает const. что const означает, что memcpy() должен будет рассматривать второй указатель, source, как немодифицируемый, это не означает, что память, на которую указывает, является немодифицируемой. Если вы солжете memcpy() и скажете ему, что целевой адрес безопасно отделен от исходного, он вам поверит. Неопределенное поведение означает, что если регионы действительно перекрываются, любая ошибка, которая может (но необязательна) произойти, является вашей собственной ошибкой, а не ошибкой в ​​​​библиотеке. - person This isn't my real name; 14.05.2013
comment
@ElchononEdelson Разве const void * source не означает, что указываемая память не поддается изменению? И разве объявление немодифицируемого указателя не void *const source? - person Rüppell's Vulture; 14.05.2013
comment
@Rüppell'sVulture, интерфейс memcpy - это void *memcpy(void * restrict s1, const void * restrict s2, size_t n);restrict), поэтому в нем четко указано, что два переданных ему указателя не должны быть псевдонимами. Если вы передаете их таким образом, что это не гарантируется, это ваша вина. И квалификация const не означает, что цель не поддается изменению. Это просто означает, что memcpy не разрешено изменять этот указатель. Это контракт, ничего больше. - person Jens Gustedt; 14.05.2013
comment
@perreal Вы имеете в виду, что мы можем избежать использования const void* source, даже если source не является const-qualified в его объявлении? - person Rüppell's Vulture; 14.05.2013
comment
Нет, const void *source означает, что память, на которую указывает указатель, должна рассматриваться как немодифицируемая с помощью этого указателя. - person This isn't my real name; 14.05.2013
comment
@Rüppell'sVulture, const в сигнатуре функции означает, что функция не намерена изменять этот параметр. это не означает, что переданный объект неизменен. - person perreal; 14.05.2013
comment
@ElchononEdelson только через этот указатель или через любой указатель? - person Rüppell's Vulture; 14.05.2013
comment
@ElchononEdelson Должен ли я заключить, что это означает, что указываемая память должна рассматриваться как немодифицируемая через этот указатель. но другие указатели могут ее изменять? - person Rüppell's Vulture; 14.05.2013
comment
@Rüppell'sVulture внутри функции память, которая обещает быть неизменной, должна оставаться неизменной, даже если вы обращаетесь к ней через другой указатель (который вы действительно должны объявить как указатель на константную память). - person Koushik Shetty; 14.05.2013
comment
const не имеет отношения к этому вопросу. - person R.. GitHub STOP HELPING ICE; 14.05.2013
comment
@R.. Почему так? Что, если мы не хотим, чтобы функция, которой передается аргумент указателя, изменяла исходное значение по указанному адресу? Пожалуйста, уточните немного. - person Rüppell's Vulture; 14.05.2013
comment
Передача указателя на константу не имеет ничего общего с тем, может ли вызываемая сторона изменять данные, на которые указывает, определенным образом. Возврат к указателю на неконстантную версию типа совершенно четко определен, и изменение объекта с помощью результирующего указателя также совершенно четко определено, пока указывает исходный объект to не был const-квалифицированным. Другими словами, в int x=0; foo((const int *)&x); функция может законно изменять x; только в случае const int x=0; foo(&x) модификация приводит к неопределенному поведению. - person R.. GitHub STOP HELPING ICE; 14.05.2013