Допустимо ли использовать «ограничить», когда есть возможность перераспределения памяти (изменения указателя)?

#c #restrict-qualifier

Вопрос:

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

У меня есть функция, которая передается двумя строками (символ*), а также int (int*).

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

 char* concatFunc (char* restrict first_string, char* const restrict second_string, int* const restrict offset) {
size_t block = 200000;
size_t len   = strlen(second_string);
char* result = first_string;

if(*offset len 1>block){
  result = realloc(result,2*block);
}
memcpy(result *offset,second_string,len 1);
*offset =len;
return resu<
}
 

Указанная выше функция неоднократно вызывается другими функциями, которые также используют ключевое слово restrict.

 char* addStatement(char* restrict string_being_built, ..., int* const restrict offset){
  char new_statement[30] = "example additional text";

  string_being_built = concatFunc(string_being_built,amp;new_statement,offset);
}
 

Таким образом, в функции объединения первая строка ограничена (это означает, что указанная память не будет изменена из другого места). Но тогда, если я перераспределяю указатель, который является его копией, приведет ли это к неопределенному поведению или компилятор достаточно умен, чтобы учесть это?

В принципе: Что происходит, когда вы ограничиваете параметр указателя, но затем меняете указатель.

Комментарии:

1. OT: Если ваша целевая строка когда-либо достигнет 2 * block длины, вы переполните result буфер, потому что вы никогда не увеличиваете ее до 2 * block байтов.

2. @AndrewHenle да, на самом деле логика другая, я просто хотел, чтобы здесь все было просто

3. Не char* result = first_string; нарушает ли правило ограничения с самого начала? Также обратите внимание, как memcpy требуется, чтобы область памяти не перекрывалась (в противном случае это неопределенное поведение). Кроме этого, я не думаю, что ключевое слово restrict здесь что-то добавляет. Возможно, это не так (если result не использовалось в качестве псевдонима), но я также не вижу никаких потенциальных оптимизаций, связанных с ограниченными указателями.

4. @Yun «Я также не вижу никаких потенциальных оптимизаций, вытекающих из ограниченных указателей». — > > *offset =len; похоже *offset=*offset len; и может воспользоваться тем, что предыдущий memcpy() не изменил результат предыдущих *offset вызовов. Без restrict в int* const restrict offset , *offset=*offset len; пришлось бы ссылаться offset на чтение *offset .

5. @DoritoJohnson В стороне: 2 const в char* concatFunc (char* restrict first_string, char* const restrict second_string, int* const restrict offset) { не служат здесь никакой цели, чтобы продемонстрировать проблему. Яснее продемонстрировать эту проблему без них. Удаление их не изменяет сигнатуру функции concatFunc() .

Ответ №1:

Что происходит, когда вы ограничиваете параметр указателя, но затем изменяете указатель.

Это зависит от того, как был изменен указатель — и в данном случае memcpy() риски UB.

С char* result = first_string; , наследует restrict от char* restrict first_string .

После result = realloc(result,2*block); , result как и раньше, и доступ через result не противоречит доступу через second_string или offset или result является новой памятью, и доступ через result не противоречит доступу через second_string или offset .

Тем не менее, может ли компилятор знать, что у вновь назначенного result есть одно из двух вышеперечисленных свойств realloc() ? В конце концов, realloc() это может быть определенная пользователем функция, и компилятор не должен предполагать result , что теперь у restrict него больше есть это свойство.

Таким образом memcpy() , находится в опасности.


достаточно ли умен компилятор, чтобы учесть это?

Я не вижу, что он может, кроме как предупредить об memcpy() использовании.

Конечно, ОП может использовать memmove() вместо memcpy() этого, чтобы избежать беспокойства.


Как я вижу, упрощенным примером может быть:

 char* concatFunc (char* restrict first_string, char* restrict second_string) {
  int block = rand();

  first_string = foo(first_string, block);

  // first_string at this point may equal second_string,
  // breaking the memcpy() contract

  memcpy(first_string, second_string, block);
  return first_string;
}
 

Или еще проще

 char* concatFunc (char* /* no restrict */ first_string, char* restrict second_string) {
  return memcpy(first_string, second_string, 2);
}
 

Комментарии:

1. «И все же может ли компилятор знать, что вновь назначенный результат обладает одним из двух вышеперечисленных свойств realloc()? В конце концов, realloc() может быть определенной пользователем функцией, и компилятор не должен предполагать, что результат теперь больше имеет свойство restrict.» —> Разве указание указателя с ограничением не гарантирует от программиста компилятору, что это свойство выполняется? Другими словами, компилятор может предположить restrict , что он использовался правильно, и ему не нужно ничего проверять или выводить.

2. @Yun Да и нет. С неизменным first_string , да, я согласен с вами. С измененным first_string (через result = first_string; ... result = realloc(result, 2*block); ) я полагаю, что компилятор может и действительно предполагает restrict , что это все еще применимо. Но проблема здесь в том, что код полагается на realloc() то, чтобы действовать так, как мы «знаем», когда защитное кодирование не предполагает этого.

3. @Yun Конечная мысль: параметры функции следует использовать restrict , когда ясно , что перекрытие не требуется и поддерживается на протяжении всего кода. ИМО, использование результата невыполнения static функции не обеспечивает этой гарантии.