1

Today I tried to solve a Quiz from Here and when I reached the Question 3, there was the following code:

#include <stdlib.h>

int main(void){
    int *pInt;
    int **ppInt1;
    int **ppInt2;

    pInt = (int*)malloc(sizeof(int));
    ppInt1 = (int**)malloc(10*sizeof(int*));
    ppInt2 = (int**)malloc(10*sizeof(int*));

    free( pInt );
    free( ppInt1 );
    free( *ppInt2 );
}

And the Question was:

Choose the correct statement w.r.t. above C program:

A - malloc() for ppInt1 and ppInt2 isn’t correct. It’ll give compile time error.
B - free(*ppInt2) is not correct. It’ll give compile time error.
C - free(*ppInt2) is not correct. It’ll give run time error.
D - No issue with any of the malloc() and free() i.e. no compile/run time error

Because of this line:

free(*ppInt2);

Which for what I understand suggests that, there will be no compile or run time error, I decided that

free(*ppInt2)

is not correct.

But because there is no compile/run time errors here, makes Answers B and C wrong.

The Author says that the accepted Answer is:

D - No issue with any of the malloc() and free() i.e. no compile/run time error.

Now here is my Question, why is there no issue, because doing this:

free( *ppInt2 );

Valgrind reports:

==9468== Memcheck, a memory error detector
==9468== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al.
==9468== Using Valgrind-3.13.0 and LibVEX; rerun with -h for copyright info
==9468== Command: ./program
==9468== 
==9468== Conditional jump or move depends on uninitialised value(s)
==9468==    at 0x4C30CF1: free (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==9468==    by 0x1086C1: main (program.c:14)
==9468==  Uninitialised value was created by a heap allocation
==9468==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==9468==    by 0x108696: main (program.c:10)
==9468== 
==9468== 
==9468== HEAP SUMMARY:
==9468==     in use at exit: 80 bytes in 1 blocks
==9468==   total heap usage: 3 allocs, 2 frees, 164 bytes allocated
==9468== 
==9468== 80 bytes in 1 blocks are definitely lost in loss record 1 of 1
==9468==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==9468==    by 0x108696: main (program.c:10)
==9468== 
==9468== LEAK SUMMARY:
==9468==    definitely lost: 80 bytes in 1 blocks
==9468==    indirectly lost: 0 bytes in 0 blocks
==9468==      possibly lost: 0 bytes in 0 blocks
==9468==    still reachable: 0 bytes in 0 blocks
==9468==         suppressed: 0 bytes in 0 blocks
==9468== 
==9468== For counts of detected and suppressed errors, rerun with: -v
==9468== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)

I thought that the right free call should be:

free( ppInt2 );

Tested on Linux mint 19, GCC-8 and valgrind-3.13.0

9
  • 3
    free( *ppInt2 ); is undefined behavior, there is no concept of run time error in C standard. Your quizz cast return of malloc so I give them 0. Commented Jul 6, 2018 at 11:04
  • none of the options are correct statements Commented Jul 6, 2018 at 11:04
  • 1
    @Michi : then your analysis is correct :) Commented Jul 6, 2018 at 11:07
  • 1
    @Michi - to spare you another problem: The very next question uses an incorrect format specifier for printing a sizeof value. That makes all of those answers wrong. That site might not be the best place to learn C. :-) Commented Jul 6, 2018 at 11:40
  • 1
    Again and again "geeksforgeeks" proves to be a trash site, with amateurs writing articles and nobody proof-reading. Just don't use that site at all. Commented Jul 6, 2018 at 11:41

2 Answers 2

7

Answer C is closest to being correct. The line

free( *ppInt2 );

is definitely incorrect. The error is not one that can be detected by the compiler. But it is quite likely to cause a run-time error. (But it is not guaranteed to cause a run-time error. More on this below.)

The rule for malloc and free is pretty simple: every pointer you hand to free must be exactly one that you received from a previous call to malloc (or calloc, or realloc). In the code, the malloc and free calls for pInt and ppInt1 correctly follow this rule. But for ppInt2, the pointer returned by malloc is assigned to ppInt2, but the pointer handed to free is *ppInt2, the value pointed to by ppInt2. But since *ppInt2 -- that is, the value pointed to by ppInt2 -- hasn't been initialized in any way, it's a garbage value, which free is likely to crash on. The end result is more or less exactly as if you had said

int main()
{
    int *p;
    free(p);     /* WRONG */
}

But, again, a crash is not guaranteed. So the more correct answer would therefore be worded as

C' - free(*ppInt2) is not correct. It’ll likely give a run time error.

I'm afraid that someone who says that answer D is correct may not really know what they are talking about. I would suggest not continuing further with this quiz -- who knows how many other wrong or misleading answers it contains?

It's always hard to understand undefined behavior, because undefined behavior means that anything can happen, including nothing. When someone says "I heard that doing X was undefined, but I tried it, and it worked fine", it's just like saying "I heard that running across a busy street was dangerous, but I tried it, and it worked fine."

The other thing about undefined behavior is that You have to think about it, and understand it, carefully. Pretty much by definition, no language translation tool -- no C compiler or other tool -- is guaranteed to warn you about it. You have to know what's undefined, and what to therefore steer clear of. You can't say "Well, my program compiles without errors or warnings, and it seems to work, so it must be correct." In other words, you can't try to foist the "correct vs. incorrect" determination off onto the machine -- you have to own this distinction.


But perhaps you knew all that. Perhaps the real question is simply, "If answer C is correct, how can the program not fail with a run-time error, in fact how can it repeatedly not fail?" This question has two answers:

  1. As mentioned, undefined behavior means anything can happen, including nothing (i.e. no errors), including nothing on multiple successive runs.

  2. On many systems, the first time malloc gives you a pointer to some brand-new memory, it's always all-bits-0 (that is, more or less as if you'd called calloc). This is absolutely not guaranteed by the C Standards -- you should never depend on it -- but on those systems, it's so likely that it might as well be guaranteed. Furthermore, on virtually all systems, a pointer value that's all-bits-0 is a null pointer. So, and again only on those particular systems, and again only the first time malloc gives you a pointer to brand-new memory, the code ppInt2 = malloc(10 * sizeof(int*)) will give you 10 null pointers. And since free is defined as doing nothing if you pass it a null pointer, in this specific case, free(*ppInt2) will never fail, not even at run time. (Perhaps this is what the person setting the quiz had in mind, because if you make these additional assumptions, answer C as written is basically not correct, and answer D basically is -- I hate to admit this -- more or less accurate.)

Returning to an earlier analogy, if someone makes these additional assumptions, and notices that the code never fails, and tries to claim that answer D is correct instead, it's basically like saying, "I heard that running across the street was dangerous, but I tried it in the middle of the night, and it worked fine. I even ran back and forth ten times. I never got hit by a car, not even once". And, unfortunately, there are programmers out there who follow similar logic, who will write programs that do the C programming equivalent of running across the street at all times. And these programmers then complain, as if it's not their fault, when inevitably their luck runs out and there's a horrible, fatal crash.

Sign up to request clarification or add additional context in comments.

10 Comments

Well, imagine yourself how many people are making that Quiz :)).
The error is not one that can be detected by the compiler : Well, the funny thing by GCC is, that it takes its own decision when comes to something like that. Take a look Here, when you compile the same program with -O3. ==>> total heap usage: 1 allocs, 0 frees, 80 bytes allocated instead of ==>>> total heap usage: 3 allocs, 2 frees, 164 bytes allocated
for completeness : free can also be called for a null pointer without invoking undefined behavior. That's the principal reason that in many cases the shown code will not cause a runtime error (if ppInt2 happens to point to zero initialized memory).
@SanderDeDycker Ah, right. Good point. (Which, returning to my analogy, is sort of like saying "I heard that running across a busy street was dangerous, but I tried it in the middle of the night, and it worked fine. I even tried it ten times.")
@SanderDeDycker If you add some more information Like Here you will get those free's but still an invalid free will take place.
|
1

Let's sort this out:

  1. There is no compile time error here
  2. There is no (ISO C standard specified) runtime error here, either, since there are next to none runtime errors in C. Essentially, the only runtime errors are errors (returned) from standard library functions.
  3. The free(*ppInt2) is undefined behaviour. Anything can happen. Compiler may delete it, or it can even delete the whole main(), or much, much worse. If it just leaves it as-is, the free() function itself may also do anything - ignore, crash, report an error, mess up its bookkeeping by trying to free given pointer...
  4. This is a coding error. Unfortunately, like many in C, it is not caught by the language compiler or it's runtime/standard library.

The fact that Valgrind catches this is nice selling point for the tool, but, it's not part of C language.

2 Comments

Actually, I'd say Valgrind is as much a part of the C language as gcc is.
@SteveSummit Well, in the sense that you should use Valgrind, if you can, sure, you could (should? :) ) say that. But, it really isn't part of C (the language) and is available on only a handful of platforms - while C is available everywhere and gcc is available for a lot of platforms.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.