2017-01-09 6 views
0

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

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

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

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

#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); 
} 

Output()

Адрес себе: +12345
ли ограничить стоп неопределенное поведение?
Адрес сам по себе: 12345
stop undefined bop undefined поведение?
Адрес себе: 12345

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

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

+0

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

+1

«Ограничение не препятствует доступу к перекрывающейся памяти с помощью« memcpy »- почему вы ожидали этого? И что вы ожидали, когда 'memcpy' попытался получить доступ к перекрывающейся памяти в любом случае (потому что вы сказали ему получить доступ к перекрывающейся памяти)? – immibis

+1

'printf ("% d \ n ", & сам);' вызывает неопределенное поведение, '% d' выводит только' int', но аргумент задается 'char *' –

ответ

2

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

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

  • читать из [0] в регистр 1
  • Записать в B [0] из регистра 1
  • Чтение из А [1] в регистр 1
  • Запись в B [1] из регистра 1
  • считываются из [2] в регистр 1
  • Запись в B [2] из регистра 1
  • читать из [ 3] в регистр 1
  • Запись на B [3] из регистра 1
  • ...

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

  • читать из [0] в регистр 1
  • читать из [1] в регистр 2
  • считываются из [2] в регистр 3
  • читать из [3] в регистр 4
  • Запись в B [0] из регистра 1
  • Запись в B [1] из регистра 2
  • Запись в B [2] из регистра 3
  • Запись в B [3] из регистра 4
  • ...

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

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


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

2

В memcpy функция копирует п байтов из области памяти SRC в области памяти Dest непосредственно.

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

memmove использует буфер так что нет никакого риска дублирования памяти.

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

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

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

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

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

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

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

1

restrict никогда не прекращает неопределенное поведение. Фактически это вводит неопределенное поведение в некоторых случаях. Если restrict удаляется из фрагмента кода, который не имеет UB, тогда код все еще не имеет UB; но обратное неверно.


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

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

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

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

+0

* «... в некоторых случаях он вводит неопределенное поведение» * - это заставило меня улыбнуться. – jww