-2

Why this program doesn't crash, is it undefined behaviour ?

int main()
{
   char* c;
   if (c) {
        printf("called free\n");
        free (c);
   }
   else {
        printf("not called free\n");
   }

   printf("not crashed\n");
   c = strdup("someString");

   if (c) {
        printf("called free\n"); 
        free(c);
        c = NULL;  // why is this needed for last if (c) 
   }
   
   if (c) {
        free(c);
   }
   printf("still not crashed\n");

    return 0;
}
  1. Is any possibility that this program will ever crash ?
  2. Should I always make such a if (c) verification before I call free on char* ?
  3. why without c=NULL free is called ?
Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
lkm lkm
  • 11
  • 2
  • 5
    Undefined Behaviour includes behaviour that appears to work correctly. If it would always crash, that would be defined behaviour. – pmacfarlane Mar 05 '23 at 13:25
  • 1
    Side note: When your program crashes, then all data in the output buffer that has not been flushed will probably be lost (i.e. will never be printed). Therefore, before doing something that will potentially crash the program, I suggest that you flush the output buffer using `fflush(stdout);` to ensure that all output is printed to the screen beforehand. Or better: Print all diagnostic output to `stderr` instead of `stdout`, for example using `fprintf(stderr,"not crashed");`. In contrast to `stdout`, `stderr` is generally unbuffered and therefore does not have the problem mentioned above. – Andreas Wenzel Mar 05 '23 at 13:39

3 Answers3

2

Is any possibility that this program will ever crash ?

  1. Yes, there is. Your code invokes undefined behaviour;

behavior, upon use of a nonportable or erroneous program construct or of erroneous data, for which this International Standard imposes no requirements.¹

Anything can happen, and that includes a crash.


Should I always make such a if (c) verification before I call free on char* ?

No, a call to free() with a NULL pointer constant is a NOP, i.e. no operation is performed. So it's unnecessary.

If ptr is a null pointer, no action occurs.²


why without c=NULL free is called ?

Because the free() function doesn't set the original pointer to NULL. c is still pointing to the same memory location, but it is invalid for c to access it anymore.

The free function causes the space pointed to by ptr to be deallocated, that is, made available for further allocation.³

Without setting c to NULL, a subsequent call to free() would be freeing already freed memory, which also invokes undefined behaviour.

Otherwise, if the argument does not match a pointer earlier returned by a memory management function, or if the space has been deallocated by a call to free or realloc, the behavior is undefined.⁴

Footnotes:

[1] - [2] - [3] - [4] - The C11 Standard, 7.22.3.3 The free function.

Harith
  • 4,663
  • 1
  • 5
  • 20
  • char* c; is not automatically points to NULL, is it ? – lkm lkm Mar 05 '23 at 13:49
  • 1
    @lkmlkm: The initial value of `c` will be [indeterminate](http://port70.net/~nsz/c/c11/n1570.html#3.19.2), which means that it may happen to be `NULL` or it may happen to be another value or it may not be a valid value at all (i.e. it may have a trap representation). – Andreas Wenzel Mar 05 '23 at 13:55
  • No, pointers aren't automatically initialized to `NULL`. – Harith Mar 05 '23 at 13:56
2

First, some background: there are three kinds of pointer values:

  1. null pointers: either because you explicitly initialized to NULL, or because it was a global (or static) variable which is automatically initialized to a null pointer.
  2. valid pointers: a pointer that you have explicitly set to point to something: either by using &, or by calling malloc or realloc or strdup, and as long as the pointer has not become invalid.
  3. invalid pointers: pointers that are local ("automatic") variables that have not been initialized; pointers that were set (using &) to point to local variables in functions ("stack frames") that are no longer active; pointers that were once allocated using malloc or the others, but that have since been deallocated using free, or reallocated using realloc.

You can tell if a pointer is in category 1 using an ordinary comparison: if(p == NULL), if(p != NULL).

But the key point is that there is no programmatic way of distinguishing between pointer values in categories 2 and 3. That is, there is no way to write if(valid(p)) or if(invalid(p)).

So the only way to avoid using invalid pointers is to keep track, yourself, somehow, of which pointers are valid and which are not. One strategy for doing this, pretty widely recommended as a good habit, is to set pointer variables to NULL whenever you know they're invalid.

See also this answer and this answer.

Now, for some specific answers to your code and questions:

int main()
{
char* c;

So c is a local variable. Since it is not initialized, it is unquestionably invalid (that is, category 3).

if (c) {
printf("called free\n");
free (c);
}
else {
printf("not called free\n");
}

This is a strange piece of code. It seems to acknowledge the possibility that the uninitialized pointer c might reliably start out as a null pointer, which as I just said is not the case. Also, this code is actually arranged, if anything, to maximize the possibility of a crash. If, by chance, c starts out as a null pointer, free is not called — but it turns out that free is guaranteed to behave gracefully — by quietly doing nothing — if it's passed a null pointer. Only if c is non-null — meaning that it's guaranteed to be invalid — is it handed to free, where it's virtually guaranteed to cause trouble.

c = NULL; // why is this needed for last if (c)

As mentioned, it's a good habit to set pointers to NULL after freeing them. A null pointer is testably invalid, while a non-NULL but freed pointer is undetectably invalid.

  1. Is any possibility that this program will ever crash ?

Yes, it is quite likely to crash, since if tries to free invalid, uninitialized pointer, and (without that "why is this needed?" line), it frees a pointer twice.

If you ran this code, and it didn't crash, that's kind of interesting, but it doesn't prove anything. Code that exhibits undefined behavior, such as trying to use the value of an uninitialized or otherwise invalid pointer, is not guaranteed to work. But it is not guaranteed not to work. "Undefined behavior" means that anything can happen, including code that appears to magically work. Undefined behavior does not mean that code is guaranteed to crash, or anything like that.

  1. Should I always make such a if (c) verification before I call free on char* ?

You can, but I don't think it does what you think it does.

It's not necessary, because as I mentioned, if you have a pointer that's already null, it's harmless to call free on it. So the test, in and of itself, doesn't protect you from anything.

If you thought that the if(c) test did protect you from something, like by making sure you don't accidentally free an invalid pointer, it's not necessarily any good for that at all — unless you're religious about always setting pointers to NULL when you know they've become invalid.

Steve Summit
  • 45,437
  • 7
  • 70
  • 103
  • Another issue is that even if one constructed a malloc/free implementation so that `free` would harmlessly do nothing if passed an invalid pointer (which would be possible), there would be no way of knowing whether calling `free()` with an unknown pointer value might free a region of storage which is going to be used for some other purpose, thus causing a use-after-free scenario which cannot be practically trapped. – supercat Mar 06 '23 at 23:43
  • Even if a system could trap any attempts to access something that isn't a valid allocated region, passing an unknown pointer to `free()` between two other allocation requests may result in the second request yielding a pointer to the same storage as the first. Attempts to write the storage returned by the first allocation may stomp the storage pointed to by the second, and vice versa, but such accesses would not be trapped because they would all be accesses to seemingly valid storage. – supercat Mar 06 '23 at 23:46
1

This code snippet

   char* c;
   if (c) {
        printf("called free\n");
        free (c);
   }
   else {
        printf("not called free\n");
   }

invokes undefined behavior because the pointer c is uninitialized.

As for this code snippet

   if (c) {
        printf("called free\n"); 
        free(c);
        c = NULL;  // why is this needed for last if (c) 
   }
   
   if (c) {
        free(c);
   }

then if the pointer c will not be set to NULL

c = NULL;  // why is this needed for last if (c) 

then in the next if statement there will be an attempt to free already freed memory that again invokes undefined behavior.

Undefined behavior does not necessary crash a program. For example in the first code snippet the pointer c can be set internally to NULL.

Pay attention that you may call the function free for a null pointer. In this case no action occurs.

Vlad from Moscow
  • 301,070
  • 26
  • 186
  • 335
  • char* c; fee(c); free(c); also undefined behaviour and might crash ? I didn't see crash for such code only when there was actually memory to be freed and free() was called multiple times. – lkm lkm Mar 05 '23 at 13:39
  • @lkmlkm Of course it is undefined behavior. The code can be executed successfully if by accident the pointer c is set to NULL. You may call free for a null pointer. – Vlad from Moscow Mar 05 '23 at 13:41