开发者

Should I enforce realloc check if the new block size is smaller than the initial?

开发者 https://www.devze.com 2022-12-25 14:38 出处:网络
Can realloc fail in this case? int *a = NULL; a = calloc(100, sizeof(*a)); printf(\"1.ptr: %d\\n\", a); a = realloc(a, 50 * sizeof(*a));

Can realloc fail in this case?

int *a = NULL;

a = calloc(100, sizeof(*a));
printf("1.ptr: %d\n", a);

a = realloc(a, 50 * sizeof(*a));
printf("2.ptr: %d\n", a);

if(a == NULL){
    printf("Is it possible?\n");
}

return (0);

}

The output in my case is:

1.ptr: 4072560
2.ptr: 4072560

So 'a' points to the same adress. So should I enforce realloc check?

Later edit:

  • Using MinGW compiler under Windows XP.
  • Is the behaviour similar with gcc on Linux ?

Later edit 2: Is it OK to check this way ?开发者_运维技巧

int *a = NULL, *b = NULL;

a = calloc(100, sizeof(*a));
b = realloc(a, 50 * sizeof(*a));

if(b == NULL){
    return a;
}
a = b;
return a;


Yes, you should always enforce a check on realloc, or any other memory allocation for that matter.

The current behavior of re-using the same address is an implementation detail that should not be relied upon. Doing so is just opening yourself up for bugs when either the library switches it's implementation or you move to a new platform.

Is it likely this will ever fail? Probably not, I'd be astounded if you could find a case that it does. However that doesn't mean it won't. Wrapping realloc in a function which automatically does the check for every operation is simple enough that there's no reason to not do it.

void* xrealloc(void* ptr, size_t size) {
  ptr = realloc(ptr, size);
  if ( !ptr ) {
    exit(EXIT_FAILURE);
  }
  return ptr;
}


It would be surprising if realloc failed when passed a size smaller than the original allocation, but nothing in the C standard (7.20.3.4) guarantees that it will always succeed:

The realloc function deallocates the old object pointed to by ptr and returns a pointer to a new object that has the size specified by size. The contents of the new object shall be the same as that of the old object prior to deallocation, up to the lesser of the new and old sizes. Any bytes in the new object beyond the size of the old object have indeterminate values.

If ptr is a null pointer, the realloc function behaves like the malloc function for the specified size. Otherwise, if ptr does not match a pointer earlier returned by the calloc, malloc, or realloc function, or if the space has been deallocated by a call to the free or realloc function, the behavior is undefined. If memory for the new object cannot be allocated, the old object is not deallocated and its value is unchanged.

Returns

The realloc function returns a pointer to the new object (which may have the same value as a pointer to the old object), or a null pointer if the new object could not be allocated.

A very simple conforming implementation of realloc would be this:

void *realloc(void *ptr, size_t size)
{
    void *new_ptr= malloc(size);
    if (new_ptr && ptr)
    {
        size_t original_size= _get_malloc_original_size(ptr);
        memcpy(new_ptr, ptr, min(original_size, size));
        free(ptr);
    }

    return new_ptr;
}

Under low memory conditions (or any conditions under which malloc would return NULL), this would return NULL.

It would also be a very simple optimization to return the same pointer if the size of the original allocation is greater than or equal to the requested size. But nothing in the C Standard dictates that.


It is good practice to check the return value of realloc in any case (the specification doesn't say you are safer if you shrink your memory block than if you expand it). But you should be careful NOT to lose the initial pointer (which you do, in your case), as you'd then be completely unable to release it.


The C99 standard §7.20.3.4 (realloc) says:

The realloc function deallocates the old object pointed to by ptr and returns a pointer to a new object that has the size specified by size. The contents of the new object shall be the same as that of the old object prior to deallocation, up to the lesser of the new and old sizes. Any bytes in the new object beyond the size of the old object have indeterminate values.

If ptr is a null pointer, the realloc function behaves like the malloc function for the specified size. Otherwise, if ptr does not match a pointer earlier returned by the calloc, malloc, or realloc function, or if the space has been deallocated by a call to the free or realloc function, the behavior is undefined. If memory for the new object cannot be allocated, the old object is not deallocated and its value is unchanged.

Returns

The realloc function returns a pointer to the new object (which may have the same value as a pointer to the old object), or a null pointer if the new object could not be allocated.

Note that the old object is deallocated; the new object may happen to point to the same location as the old. And there could be problems. It is pretty unlikely, but it is far simpler to go with a rule 'always' than to have odd exceptions.

The normal counter-argument is "if this cannot fail, it means I have an error path that I cannot test". Up to a point, that is true. However, it could be that there has been some trampling of the memory so that the allocation cannot succeed - because the control information has been corrupted. More likely you'll just get a core dump, but maybe the code is robust enough to be able to avoid that. (I assume the hard coded 100 and 50 are for purposes of asking the question; real life code would not over-allocate when it knows how much it really needs.)

Where the two calls to 'realloc()' are adjacent, as here, there is very little room for anything to go wrong. However, real life working code would have some operations in between the two - and that code could cause the second 'realloc()' to fail.

Regarding your 'Edit 2'...

The code might be better written as:

if (b != NULL)
    a = b;
return a;

But the basic concept is OK. Note that the standard explicitly says that the original allocation is safe if the new one cannot be created.


The time it takes to do the check is so small compared to the time spent in realloc() that I can't even see why it'd be a problem. Or do you want to reduce the number of lines of code?


realloc() can return NULL easily enough on size reduction.

void *ptr = malloc(10);
ptr = realloc(ptr, 0);
if (ptr == NULL) {
  puts("Failure because return value is NULL? - not really");
}

realloc(any_pointer, 0) could return NULL or maybe some not-NULL pointer, it is implementation defined.

That is why realloc()/malloc() failure should not be a simple test of if (ptr == NULL) but

void *ptr = malloc(newsize); // or realloc(..., newsize)
if (ptr == NULL && newsize > 0) {
  exit(0); // Handle OOM;
}

Because of this ambiguity, should code want to make a realloc() wrapper, recommend something like:

void *xrealloc(void *ptr, size_t newsize, bool *falure) {
  *failure = 0;
  if (newsize > 0) {
    void *tmp = realloc(ptr, newsize);
    if (tmp == NULL) {
      *failure = 1;
      return ptr;  // old value
    }
    return tmp;  // new value
  } 
  free(ptr);
  return NULL; // new value
  }

Getting NULL on a realloc() with reduced size therefore is not really a failure and so this answer only tangentially applies, but OP's question was "... enforce realloc check if the new block size is smaller than the initial?" and then used the less trustworthy if (ptr == NULL) paradigm.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号