#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
функции не обеспечивает этой гарантии.