Is it valid to use "restrict" when there is the potential for reallocating memory (changing the pointer)?
Asked Answered
E

1

6

I am attempting some optimization of code, but it is hard to wrap my head around whether "restrict" is useful in this situation or if it will cause problems.

I have a function that is passed two strings (char*) as well as an int (int*).

The second string is copied into memory following the first string, at the position indicated by the int. If this would overrun the allocation of memory for the first string, it must reallocate memory for the first string before doing so. A new pointer is created with the new allocation, and then the original first string pointer is set equal to it.

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 result;
}

The above function is repeatedly called by other functions that are also using the restrict keyword.

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,&new_statement,offset);
}

So in the concatFunc the first_string is restricted (meaning memory pointed to will not be changed from anywhere else). But then if I am reallocating a pointer that is a copy of that, is that going to cause undefined behavior or is the compiler smart enough to accommodate that?

Basically: What happens when you restrict a pointer parameter, but then change the pointer.

Exuviae answered 12/8, 2021 at 20:38 Comment(6)
OT: If you target string ever reaches 2 * block in length, you're going to overflow the result buffer because you never grow it past 2 * block bytes.Abstracted
@AndrewHenle yes the actual logic is different I was just keeping it simple hereExuviae
Doesn't char* result = first_string; break the restrict rule to begin with? Also note how memcpy requires that the memory location do not overlap (otherwise it's undefined behavior). Other than that, I don't think the restrict keyword adds anything here. It may not be wrong (if result wasn't used as an alias), but I don't see any potential optimizations resulting from restricted pointers either.Masterpiece
@Masterpiece " I don't see any potential optimizations resulting from restricted pointers either. " --> *offset+=len; is like *offset=*offset + len; and can take advantage that the prior memcpy() did not change the result of the earlier *offset calls. Without restrict in int* const restrict offset, *offset=*offset + len; would have to reference offset to read *offset.Mady
@DoritoJohnson Aside: The 2 const in char* concatFunc (char* restrict first_string, char* const restrict second_string, int* const restrict offset) { serve no purpose here to demonstrate the issue. Clearer to demo this issue without them. Dropping them does not change the function signature of concatFunc().Mady
@chux-ReinstateMonica I see, thank you! Also, it turns out that aliasing a restricted pointer is actually fine (ref).Masterpiece
M
5

What happens when you restrict a pointer parameter, but then change the pointer.

It depends on how the pointer was changed - and in this case, memcpy() risks UB.

With char* result = first_string;, inherits the restrict of char* restrict first_string.

After result = realloc(result,2*block);, result is as before and accessing via result does not collide with accessing through second_string or offset or result is new memory and accessing via result does not collide with accessing through second_string or offset.

Yet can the compiler know the newly assigned result has those one of two above properties of realloc()? After all, realloc() might be a user defined function and compiler should not assume result now has the restrict property anymore.

Thus memcpy() is in peril.


is the compiler smart enough to accommodate that?

I do not see it can, other than warn about memcpy() usage.

Of course OP can use memmove() instead of memcpy() to avoid the concern.


As I see it, a simplified example would be:

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

Or even simpler

char* concatFunc (char* /* no restrict */ first_string, char* restrict second_string) {
  return memcpy(first_string, second_string, 2);
}
Mady answered 12/8, 2021 at 23:27 Comment(3)
"Yet can the compiler know the newly assigned result has those one of two above properties of realloc()? After all, realloc() might be a user defined function and compiler should not assume result now has the restrict property anymore." --> Isn't qualifying a pointer with restrict a guarantee from the programmer to the compiler that this property holds? In other words, the compiler may assume restrict was used correctly and doesn't need to verify or infer anything.Masterpiece
@Masterpiece Yes and no. With an unaltered first_string, yes, I agree with you. With a changed first_string (via result = first_string; ... result = realloc(result, 2*block);) one, I believe the compiler can and does assume restrict still applies. But a problem here is that code relies on realloc() acting as we "know", when defensive coding would not assume that.Mady
@Masterpiece End thought: function parameters should use restrict when it is clear no overlap is required and maintained throughout code. IMO, using the result of a non-static function does not maintain that guarantee.Mady

© 2022 - 2024 — McMap. All rights reserved.