On Thu, Sep 5, 2024 at 9:02 PM Tomek CEDRO <to...@cedro.info> wrote:

> Hello world :-)
>
> I am working more in C recently than in for instance Python. Using
> structures with pointers to structures or even pointers to functions
> with pointer parameters. The problem is for instance some pointers
> will not always be null but still invalid so the references cause
> panic and reboot.
>
> Is there any way to handle especially the worst of exceptions in any
> other way than kernel panic?
>
> For instance Python has this try-except block, C++ has this try-catch
> block. This allows handling gracefully even worst exceptions (or only
> selected ones).
>
> Is there anything like this in C / NuttX? :-)
>
> Any hints welcome :-)
> Tomek
>
> --
> CeDeROM, SQ7MHZ, http://www.tomek.cedro.info
>

Hi Tomek,

In C, if something isn't initialized, it could contain anything. Its value
is "undefined."

C doesn't have constructors and destructors like C++ that automatically set
things up and tear them down. Every variable, struct, buffer, etc., should
be assumed to contain undefined garbage until initialized explicitly. (Yeah
yeah I know globals supposedly start at all 0s... blah blah, I don't trust
that because it's often not true, especially in embedded!!!) Everything is
garbage until initialized!!

The trouble is, with pointers, that undefined value could be nonzero, so it
looks like a valid pointer until you use it and then all hell breaks loose.

Unfortunately, there is no way to test a pointer to see if it's garbage.
You can only test for NULL (which means don't dereference it) or non-NULL
(which means you should be okay to dereference it). But non-NULL could
either be "okay!" or "garbage!" So you have to be very disciplined in the
use of pointers to ensure they're not garbage.

So, the pattern to use is:

1. Always initialize everything, including always initialize pointers to
either NULL or to the object they'll point to.

2. When a pointed-to object goes out of scope or is freed, clear all
pointers that point to it right away!

When you do both of those things, you can check 'if (my_pointer == NULL)'
... before accessing it, and report an error or do some other reasonable
action. This avoids crashes. :-)

More about item #1:

If it's a structure, a buffer, etc., I always start by memset()ing it to 0.

If allocating a structure on the heap, there's a debate on whether to use
malloc(), calloc(), zalloc(), etc. IMO the best to use is calloc() because
it initializes the allocated memory to 0, so you don't have to do your own
memset(). Also on some systems, calloc() may be implemented in a way that
avoids needless memset() while still giving you a zeroed buffer. If your
program does a tremendous amount of calloc()s, this can make some slight
performance improvement over malloc() + memset().

More about item #2:

Clearing pointers when done with them:

e.g.:
free(thing);
thing = NULL;

It is best to only ever have one pointer point to something because that's
easier to keep track of, but sometimes this is not possible: Your
application's data may be some kind of hierarchical or graph type thing
with multiple structures pointing to each other in complicated ways. In
this case, you really need to track down and clear all such pointers, and
order of operations here is important!

For example, maybe you have struct a, struct b, struct c, and they point to
each other like this:

a <-> b <-> c

Meaning a has a pointer to b, b has a pointer to a, b has a pointer to c, c
has a pointer to b.

Now suppose you're going to free b.

When you architect this program, you need to think about what happens if
you free b.

Does it mean you should also free a and c?

Or does it mean a should point to c and c should point to a? Like this:

a <-> c

Or does it mean a and c are left alone but because b is gone, the links
between these objects is gone:

a   c

There's another thing to consider: what's keeping track of the existence of
these objects?

Does your program have global pointers to a, b, c?

Or do you only keep a pointer to one of them, such as b, and reach the
other ones through that?

The answer to this question will help explain what should happen when you
free b. Because if you only have the pointer to b in some global variable
and you free b and destroy your ability to reach a and c, you'll leak
memory.

Admittedly this is a lot to think about, but this is more of a
whole-program architecting of how you store and access information. It
helps to reason through your data and draw it out on paper with arrows
between objects. Each arrow is a pointer. Then start reasoning about what
happens if you add another object? If you free an object?

If you only take two things from this email, they should be:

1. Always initialize pointers to either point to a valid object or be NULL
and

2. Always clear pointers back to NULL when the object is freed or goes out
of scope

And if you only take one thing from this email, it should be:

0. Never let a pointer contain "undefined" garbage!! (or point to some
already freed thing)

Hope this helps!

Nathan

Reply via email to