Почему квалификатор limited по-прежнему позволяет memcpy получать доступ к перекрывающейся памяти?

Я хотел посмотреть, не предотвратит ли restrict доступ memcpy к перекрывающейся памяти.

Функция memcpy копирует n байт из области памяти src в область памяти назначения напрямую. Области памяти не должны перекрываться.

memmove использует буфер, поэтому нет риска перекрытия памяти.

Определитель restrict говорит, что в течение всего времени существования указателя только сам указатель или значение непосредственно из него (например, pointer + n) будет иметь доступ к данным этого объекта. Если декларация о намерениях не выполняется и доступ к объекту осуществляется с помощью независимого указателя, это приведет к неопределенному поведению.

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

#define SIZE 30

int main ()
{
    char *restrict itself;
    itself = malloc(SIZE);
    strcpy(itself, "Does restrict stop undefined behavior?");
    printf("%d\n", &itself);
    memcpy(itself, itself, SIZE);
    puts(itself);
    printf("%d\n", &itself);

    memcpy(itself-14, itself, SIZE); //intentionally trying to access restricted memory
    puts(itself);
    printf("%d\n", &itself);

    return (0);
}

Выход ()

Собственный адрес: 12345
Ограничивает ли останавливает неопределенное поведение?
Собственный адрес: 12345
Останавливает неопределенное bop неопределенное поведение?
Собственный адрес: 12345

Использует ли memcpy независимый указатель? Поскольку выходные данные определенно показывают неопределенное поведение, а restrict не препятствует доступу к перекрывающейся памяти с memcpy.

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


person Duy Đặng    schedule 09.01.2017    source источник
comment
memmove не буферизует данные, он действует так, как если бы данные были буферизованы. Это можно сделать, например, проверив, перемещаются ли данные в памяти вперед или назад, а затем скопировав их соответственно с конца на начало или с начала на конец. На практике это немного сложнее, так как копирование данных назад на передний план имеет проблемы с производительностью, но идея в этом.   -  person GaspardP    schedule 09.01.2017
comment
ограничение не предотвращает доступ к перекрывающейся памяти с помощью memcpy - почему вы ожидали, что это произойдет? И что вы ожидали, когда memcpy все равно попытается получить доступ к перекрывающейся памяти (потому что вы сказали ему получить доступ к перекрывающейся памяти)?   -  person user253751    schedule 09.01.2017
comment
printf("%d\n", &itself); вызывает неопределенное поведение, %d печатает только int, но задан аргумент char *   -  person M.M    schedule 09.01.2017
comment
Этот вопрос не имеет смысла от начала до конца. Объекты не могут изменить свой адрес, так зачем вам снова и снова печатать адрес itself?   -  person M.M    schedule 09.01.2017


Ответы (3)


Ключевое слово restrict — это подсказка, предоставляемая компилятору, позволяющая генерировать код, говорящая компилятору, что он не должен беспокоиться о возможности псевдонима указателя (два указателя с разными именами обращаются к одному и тому же адресу).

В функции, которая принимает restrict указателя, компилятор понимает, что запись в один не повлияет на другой. При копировании из местоположения A в местоположение B это означает, что он может безопасно изменить этот код:

  • Чтение из A[0] в регистр 1
  • Запись в B[0] из регистра 1
  • Чтение из A[1] в регистр 1
  • Запись в B[1] из регистра 1
  • Чтение из A[2] в регистр 1
  • Запись в B[2] из регистра 1
  • Чтение из A[3] в регистр 1
  • Запись в B[3] из регистра 1
  • ...

В эту последовательность:

  • Чтение из A[0] в регистр 1
  • Чтение из A[1] в регистр 2
  • Чтение из A[2] в регистр 3
  • Чтение из A[3] в регистр 4
  • Запись в B[0] из регистра 1
  • Запись в B[1] из регистра 2
  • Запись в B[2] из регистра 3
  • Запись в B[3] из регистра 4
  • ...

Эти две последовательности идентичны только в том случае, если A и B не перекрываются, и компилятор не будет оптимизировать вторую последовательность, если только вы не использовали restrict (или если он не догадается из контекста, что это безопасно).

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


Отвечая на ваш вопрос о производительности - если вы точно знаете, что в ваших указателях нет перекрытия, используйте memcpy. Если нет, используйте memmove. memmove обычно проверяет, есть ли совпадения, и в конечном итоге использует memcpy, если нет, но вы платите за проверку.

person GaspardP    schedule 09.01.2017

Функция memcpy копирует n байт из области памяти src в область памяти назначения напрямую.

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

memmove использует буфер, поэтому нет риска перекрытия памяти.

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

Использует ли memcpy независимый указатель? Поскольку выходные данные определенно демонстрируют неопределенное поведение, а restrict не препятствует доступу к перекрывающейся памяти с memcpy.

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

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

Ваша программа также демонстрирует неопределенное поведение в результате вычисления itself - 14 (независимо от того, разыменовывался ли результирующий указатель когда-либо). Если бы вместо этого itself указывало не менее 14 байтов внутрь выделенного объекта, так что арифметика указателя была бы допустимой, то аргументы второго вызова memcpy() снова не соответствовали бы требованиям квалификации параметров restrict, поэтому программа по этой причине тоже будет демонстрировать UB.

Я предполагаю, что memcpy имеет преимущество в производительности, поскольку копирует данные напрямую, а memmove использует буфер. Но с современными компьютерами, должен ли я игнорировать эту потенциально более высокую производительность и всегда использовать memmove, так как это гарантирует отсутствие перекрытия?

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

person John Bollinger    schedule 09.01.2017

restrict никогда не останавливает неопределенное поведение. На самом деле в некоторых случаях это приводит к неопределенному поведению. Если restrict удалить из фрагмента кода, в котором нет UB, то код по-прежнему не имеет UB; но обратное неверно.


Ваш код вызывает неопределенное поведение в этой строке:

strcpy(itself, "Does restrict stop undefined behavior?");

из-за переполнения размера выделенного буфера. После этого все ставки снимаются.

Квалификатор restrict не предотвращает переполнение буфера.

person M.M    schedule 09.01.2017
comment
... в некоторых случаях это приводит к неопределенному поведению - это заставило меня улыбнуться. - person jww; 22.02.2018