This is the mail archive of the glibc-linux@ricardo.ecn.wfu.edu mailing list for the glibc project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]

Re: malloc problem/question in 2.1.3


Kaz Kylheku wrote:

[snipped]

> > Mike,
> >
> > Do you do any XWindows programming???  One of the more
> > common and hard to track down problems are SIGSEGV's
> > caused by inadvertently freeing memory that was not previously
> > allocated.
>
> This simply means that you have bungled your logic for initializing
and
> destroying subsystems and objects.  This could be the result of poor
planning
> at the design stage.
>
> > char* ptr = NULL ;
> >
> > if (ptr != NULL) {
> >    free(ptr) ;
> >    ptr = NULL ;
> > }
>
> This is not necessary. Calling free on a null pointer is permitted by
ANSI C.
> Look it up in your library reference manual.  So you could just write:



>
>     free(ptr);
>     ptr = NULL;


I thought it was, but it's been a while since I've done any actual
programming in C; therefore, couldn't recall off of the top of my head.

A reputable book, one I'ld definitely recommend, "Advanced Programming
in the Unix Environment", Richard Stevens, 1992, eleventh printing 1996,
pp 170-171, does not explicitly say that the ptr needs to be verified
prior to invoking free, however if the ptr (i.e., block of memory
allocated) was already freed and free is called on the same again, then
this causes an error; therefore, if the logic is such that this could
happen, then the text implies that the pointer should be verified,
first.

If verifying the pointer is unnecessary, but this code is added, then
the worst it does is to add a little unnecessary code.  My point in this
respect is merely that doing this isn't always necessary; however, this
is not the point I had any real contention with.

I primarily question the supposed absolute necessity to redefine ptr to
null immediately after calling free just because free was called to
deallocate the block of memory assigned to said ptr.  This may sometimes
be necessary, but not always.

For example, there is no such need in the following function, using some
form of pseudo code:

void myfunc () {
char * ptr;

while [ some condition ] do
   ptr=malloc( desired size );
   if ptr == null then print "can't get memory for ptr" and exit;
   operate on ptr (except not freeing block of memory);
   free ptr;
done

return;
}

malloc returns null upon failure.

That's only one small snippet illustration of an application of malloc,
definitely doesn't cover all possible applications, and I was looking
for some examples (not for myself, because I don't presently need it,
would tend to use my books anyway, and instead only for the sake of this
discussion) where redefining ptr to null immediately following the call
to free in which the redefinition of ptr to null is [necessary].  Again,
this wasn't for my personal education, but instead to help clarify to
the person who initiated this thread that this isn't always required,
and clean code is better than unclean.

Also, I might tend, depending on the application and suitability, to
always use malloc on pointers which have been previously allocated
memory using malloc or calloc, or another allocation function.  For
example, if I used a pointer to iterate over or parse through an array
without changing the base of the array, then I would not allocate memory
to this pointer using malloc, before or after using the pointer to parse
the array.

Being consistent with the use of pointers, like any other elements, is
useful, safer, and can eliminate the need to verify ptr before calling
free.

Consistency, cohesion and low coupling, portability, reusability,
readability and therefore maintainability, etc. (etc. in case I'm
missing any principles) are all concepts or principles programs should
be developed and verified against.  This is the easiest way to cover all
possibilities in the least amount of words (one two line
sentence).

The point, probably because I have dealt with various contexts wrt
programming, and languages, is that context is relevant, and I tend to
try to prefer more thorough coverage, to allow the opportunity to learn
more up front.

Technically, it's true that ptr could always be redefined to null after
calling free, even when free is at the end of a function, or in the
above pseudo code example, but as it shows, doing so isn't always
necessary, and unnecessary code should ideally be excluded.

That example could also be programmed differently and if a person really
wants to init ptr to null, and redefine it to null after calling free,
then the following would be the way I'ld do it:

void myfunc () {
char * ptr;

while [ some condition ] do
   ptr=null;
   ptr=malloc( desired size );
   if ptr == null then print "can't get memory for ptr" and exit;
   operate on ptr;
   free ptr;
done

return;
}

That's opposed to the following:

void myfunc () {
char * ptr = null;

while [ some condition ] do
   ptr=malloc( desired size );
   if ptr == null then print "can't get memory for ptr" and exit;
   operate on ptr;
   free ptr;
   ptr =  null;
done

return;
}


Init'ing and redefining ptr to null is unnecessary in this piece of
pseudo code.

Programming consistently according to context or application will make
code look more professional, which is or should be beneficial in more
ways than one, and the less unnecessary code there is in a program the
easier the program is to run-time test as well as to visually verify.

I think that's part of the reason we refer to computer [science],
instead of just "programming".  Science more or less means knowledge and
refers to how and why something is done or works.

There's no confrontation when people don't treat such subjects in
absolute terms.  As illustrated, there is no absolute single answer
covering all possible cases; except that malloc returns null upon
failure or requested amount of allocated memory when successful, and
free returns indeterminate result, absolutely (or should be absolute
anyway).

To think contextually requires flexible thinking and when people aren't
flexible they can perceive others who are as conflictual.  Allow for
variations in context.  Otherwise, expect the perceptions lack of
flexibility gives rise to.

However, as there are exceptions to nearly every rule, or all, there are
definitely principles to computer science and some rigidity in this
respect is healthy, because adhering to the principles is conducive to
the production of clean programs.

Even in this, some flexibility might be worthwhile.  For example, one
principle is "one entry point, one exit or return point", but this can
lead to cluttered programs, functions, or blocks of code.   From the
conceptual point of view, the following appears valid:

int myfunc ( int myarg ) {
// Returns 0 upon success, 1 for normal error, and >1 for severe error.
if myarg == out of range value then return 2;
perform operations and if unsuccessful then return 1;
return 0;
}

That breaks the principle of one entry point and one exit or return
point, however the function will conceptually return from only one
place, based on which return is executed, and this avoids additional
code to strictly enforce one possible return point at the end of the
function, which can lead to fairly ugly code from a reading point of
view.

I've programmed scripts this way and one individual noted the
infringement of the principle, but the code is very easy to follow or
read because the additional wrapping code is eliminated, and the code
works correctly every time, handling all possible categories of
conditions.  I haven't read my Computer Science Principles book since
finishing school and maybe this is wrong style, however usually program
according to conceptual logic, because that's the way humans generally
tend to think.

This makes the program more intuitive and therefore easier to read.  As
for testing, there's little difference, because if the principle was
followed strictly, then some variable would need to be defined to return
the appropriate value.  Whether one verifies if such a variable is
correctly defined, or instead verifies a return, one is nonetheless
verifying one statement, per location.

Perhaps there is or was an underlying technical reason for requiring or
encouraging a strict one point of entry and one point of return or exit
principle, but I'ld need to verify against the book unread for over ten
years.

Another principle, though, is KISS (keep it simple silly) and the above
logic is simpler than the additional wrapping code would make it.

Flexibility is important and as long as programs work correctly and
perform in a timely manner, I prefer to emphasize intuitive logic,
eliminating conceptually unnecessary code if doing so doesn't impair the
operative correctness of the program.  Having never had a problem with
code written this way, I focus on intuition, when applicable, even if
it's not a formal CS principle.

Without going back to my old Assembly and Computer Science Principles
books, I believe this would translate correctly into Assembly, but
that's without verifying, albeit also based on programs with this style
of coding and which work correctly, at least from the user's point of
view (maybe not according to the referred-to principle).

Is that long and complicated enough for you?  You don't need to answer
and can treat that as a rhetorical question.  However, if you have a
technically valid explanation for why the above shouldn't be done, then
I'll read.

mike




Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]