Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > C Programming > To assert or not to assert...

Reply
Thread Tools

To assert or not to assert...

 
 
Gene
Guest
Posts: n/a
 
      05-12-2010
On Apr 27, 2:25*am, ImpalerCore <(E-Mail Removed)> wrote:
> I stumbled across a couple assert threads from a while ago. *I seem to
> have a hard time figuring out how to use assert properly, and I go
> back and forth over how best to represent errors from a library's
> perspective. *From what I've gathered, assert is intended to catch
> programming errors, but I don't really know what that means in the
> context when writing a library.
>
> There are three main errors that I try to handle.
>
> 1. Memory allocation faults.
>
> These faults are caused when malloc, realloc, or calloc fails. *There
> are several methods that this can be communicated to the user. *For
> functions that allocate structures, the return value represents either
> the allocated object if successful or returns NULL if allocation
> failed. *The caller of the function is responsible for handling the
> result.
>
> \code snippet
> c_array_t* numbers = NULL;
>
> numbers = c_array_new( int, 20 );
> if ( !numbers )
> {
> * /* User specified method to handle error. */
> * fprintf( stderr, "c_array_new: failed to allocate array 'numbers'!
> \n" );
> * return EXIT_FAILURE;
>
> }
>
> ...
> \endcode
>
> In this scenario, I know that I definitely don't want to assert on a
> memory fault at the library level, because in general the user may be
> able to recover from the situation, and I want to give him that
> chance.
>
> However, there are other functions that may invoke a buffer expansion
> as part of the operation, requiring a call to malloc or realloc that
> may fail. *This needs to be communicated back to the user somehow.
> Here is an example.
>
> void gc_array_push_back( c_array_t* array, void* object, size_t
> type_sz );
> #define c_array_push_back( array, object, type ) \
> * (gc_array_push_back( (array), (object), sizeof( type ) ))
>
> \question
> Should the internals of 'gc_array_push_back' use assert to check for
> null array pointers.?
>
> Consider two possible methods to verify the array parameter.
>
> void gc_array_push_back( c_array_t* array, void* object, size_t
> type_sz )
> {
> * /* variable decls */
>
> * assert( array != NULL );
>
> * /* push_back code */
>
> }
>
> vs
>
> void gc_array_push_back( c_array_t* array, void* object, size_t
> type_sz )
> {
> * /* variable decls */
>
> * if ( array )
> * {
> * * /* push_back code */
> * }}
>
> \endquestion
>
> Most of the time, an array NULL pointer is likely an error, so assert
> would help catch programming errors. *However, I can't guarantee that
> there isn't a user that would have a valid use case for having a null
> array pointer, perhaps if it's considered as an empty array in another
> structure. *So in this case, I feel that the 'if ( array )' construct
> feels right.
>
> Another question within the same context of the 'c_array_push_back'
> function is how to communicate an allocation failure if the array
> needs more memory, but the allocation fails. *At the minimum, I want
> to make sure that I don't lose any of the contents of the array
> buffer, so that the array contents remain the same. *There seem to be
> several methods to communicate that an allocation error in this case.
>
> A. *Modify 'gc_array_push_back' to respond with an error code via the
> return value (or as an additional argument to the function, but I
> really prefer not to use that style of interface).
>
> This is a common scheme that is used and recommended often, and for
> good reason. *The main drawback is that sometimes there is competition
> for the return value slot (not in the gc_array_push_back function's
> case since it returns void, but in other functions). *If I want to
> carry the error code and something else, I need to write a custom
> struct and it complicates the interface somewhat.
>
> struct c_array_return
> {
> * <data_type> data;
> * int error;
>
> }
>
> The other issue is that there can be conflicts between interpretation
> of the return value. *For example, if I make a copy of an array.
>
> \code snippet
> c_array_t* c_array_copy( c_array_t* array )
> {
> * c_array_t* copy = NULL;
>
> * if ( array )
> * {
> * * /* copy array */
> * }
>
> * return copy;}
>
> \endcode
>
> If I allow arrays that are NULL pointers, then checking the return
> value of c_array_copy for NULL is troublesome because I can't
> distinguish whether the parameter 'array' is NULL or if the allocation
> failed, which may be an important distinction.
>
> B. *Use some global variable that users should set before the
> operation and check after the operation, akin to setting errno to
> ENOMEM.
>
> One of the issues brought up before is that ENOMEM isn't portable, so
> it's not a reliable mechanism to use in of itself. *However, the
> concept is usable provided that you maintain the global state
> yourself.
>
> In my case, I have my library use a wrapper that maintains a global
> state that is accessed using a macro 'c_enomem'. *It functions
> similarly to errno, but I have control over what it does provided that
> my library calls my wrapper.
>
> \code snippet
> /* A flag that signals an out-of-memory error has occurred. */
> static c_bool gc_private_allocator_enomem = FALSE;
>
> c_bool* gc_error_not_enough_memory( void )
> {
> * return &gc_private_allocator_enomem;
>
> }
>
> #define c_enomem (*gc_error_not_enough_memory())
>
> void* c_malloc( size_t size )
> {
> * void* mem = NULL;
>
> * if ( size )
> * {
> * * mem = gc_private_allocator_ftable.malloc( size );
> * * if ( !mem ) {
> * * * gc_private_allocator_enomem = TRUE;
> * * }
> * }
>
> * return mem;}
>
> \endcode
>
> With this kind of mechanism in place, I can do something like the
> following:
>
> \code snippet
> c_array_t* numbers = NULL;
> int n;
>
> numbers = c_array_new( int, 20 );
> /* Fill up numbers array */
>
> c_enomem = FALSE;
> c_array_push_back( numbers, &n, int );
> if ( c_enomem ) {
> * fprintf( stderr, "c_array_push_back: allocation failure!\n" );
> * /* Try to recover or save gracefully if desired */}
>
> \endcode
>
> This scenario also has its drawbacks, but it's the poison that I've
> chosen particular for functions that would instead have to return
> something in addition to an error code.
>
> These are the main two error handling schemes that I'm familiar with
> that has the granularity to handle function level out-of-memory
> conditions. *Signals and callback handlers are also useful tools, but
> I've not been able to figure out a framework that seems to work at
> this level. *I've only used them as general error handlers if any
> allocation fails, rather than at specific locations.
>
> 2. Invalid arguments
>
> This is where I really struggle with what is assertable and what is
> left to the user is left to blow up.
>
> Take a function to erase an element out of an array.
>
> void gc_array_erase( c_array_t* array, size_t pos )
> {
> * if ( array )
> * {
> * * /* what to assert if anything */
> * * if ( pos < array->size )
> * * {
> * * * /* erase the element at index 'pos' */
> * * }
> * }
>
> }
>
> Does the condition 'pos < size' constitute a good assert candidate?
> There is no possible scenario where a pos >= array->size would ever do
> anything, but does the crime fit the punishment of abort in a library
> like this? *If not, is the silent treatment ok, or should an out-of-
> range error be communicated back somehow? *I can't see the pros and
> cons enough to make a decision and stick to it.
>
> I could see maybe having another layer of assert that takes the middle
> ground. *If the user wants to assert for conditions when pos is larger
> than the array size and other invalid arguments, the library could
> have something like the following.
>
> void gc_array_erase( c_array_t* array, size_t pos )
> {
> * if ( array )
> * {
> * * safe_assert( pos < array->size );
> * * if ( pos < array->size )
> * * {
> * * * /* erase the element at index 'pos' */
> * * }
> * }
>
> }
>
> This would give the users of the library some semblance of choice on
> how strict to apply the assert checking mechanism.
>
> 3. *Programming errors
>
> At the library level, do I assert things within my library interface
> that the user may have messed up? *Take for example inserting an
> object into an array in a sorted fashion.
>
> \code snippet
> void gc_array_insert_sorted( c_array_t* array, void* object,
> c_compare_function_t cmp_fn, size_t type_sz )
> {
> * if ( array )
> * {
> * * assert( c_is_array_sorted( array, cmp_fn ) );
> * * ...
> * }}
>
> \endcode
>
> In this scenario, I have a function that verifies that all the
> elements are in sorted order. *Is this an assertable offense? *It's
> certainly possible that the user may want to insert something sorted
> in the array even though the array itself is not sorted. *I don't feel
> that I have the right for my library to demand that the array is
> sorted via assert, even if the constraint of having a sorted array is
> violated. *This could be another 'safe_assert' or another level of
> assert to verify this property.
>
> There are cases that assert seems to be a good use for. *Particularly,
> if I have a valid array pointer, it's buffer better be valid too.
>
> \code snippet
> void gc_array_insert_sorted( c_array_t* array, void* object,
> c_compare_function_t cmp_fn, size_t type_sz )
> {
> * if ( array )
> * {
> * * assert( array->buffer != NULL );
> * }}
>
> \endcode
>
> This seems like a great candidate to be used in an assert, since I
> specifically designed the interface to always have at least one
> element allocated (the 'array->size' can still be zero though if no
> elements are inserted into the array).
>
> The last thing is whether it's recommended or not to append a string
> to the assert macro to help provide flavor to the condition.
> Something like
>
> \code
> assert( pos < array->size && "pos outside range: >= array->size" );
> \endcode
>
> Some things I've been pondering as of late.


The two things you're interested in -- library runtime error handling
and assertions -- are completely different as metaphors and in their
purposes. Mixing them will be confusing as a minimum.

Assertions are both documentation and debugging aids that are about
code invariants (See for example D. Griess _The Science of Computer
Programming_ on this topic). At once they document invariants and
tell you at run time if what you _thought_ was invariant really is
not; i.e. there is a gap between your mental model of the program and
what it's actually doing. An example of an invariant is that
successive pairs of elements in a sorted list are identically
ordered. If you ever find a pair that's out of order, your assertion
of "sortedness" is obviously wrong.

Runtime error checks are about assumptions rather than invariants.
You are checking assumptions about whether the caller provided valid
arguments, the system has enough memory, that I/O operations
succeeded, etc. For these purposes, you want to give the caller lots
of freedom to deal with the assumption violation in the best possible
way: report, repair, resume, recover, die gracefully are a few
options. Error callbacks are a common approach.

Cheers


 
Reply With Quote
 
 
 
 
ImpalerCore
Guest
Posts: n/a
 
      05-12-2010
On May 12, 3:18*pm, Eric Sosman <(E-Mail Removed)> wrote:
> On 5/12/2010 2:41 PM, ImpalerCore wrote:
>
>
>
> > On May 12, 1:13 pm, Eric Sosman<(E-Mail Removed)> *wrote:
> >> On 5/12/2010 11:45 AM, ImpalerCore wrote:

>
> >>> If the C standard library doesn't populate their library with asserts,
> >>> why should I populate my library with asserts at all?

>
> >> * * * Because your library is less thoroughly tested.

>
> > I can certainly agree with that understatement (as in my library is a
> > whole *lot* less tested). *Maybe I should think of assert as the
> > developer's training wheels, used to keep the developer balanced until
> > the library matures to a point that the majority of bugs have been
> > worked out and that it has a test framework to torture it and verify
> > its behavior. *At some point, maybe I could take the training wheels
> > off. *But for now, I'm definitely in the training wheels stage.

>
> * * *Hadn't thought of the training wheels analogy -- but I'm not sure
> I agree with it, either. *In the early going, assert() can truly be
> a lot like training wheels, helping you get your code to stop falling
> over. *Once you've achieved stability, though, I don't think assert()
> should fall completely out of the repertoire -- if nothing else, it
> can be a guard against mistakes introduced by changes to other parts
> of the code. *If you register each Wingding in a hash table when it
> arrives, then work on it for a while and de-register it when the
> response goes out, you may "know" that (1) an incoming Wingding is
> *not* in the hash table and (2) and an outgoing Wingding *is* there.
> If this property is important, a couple assert() macros would not be
> amiss. *Even if it's not "important" but would prompt a "What the?"
> if it somehow didn't hold, assert() can be the canary in the mine,
> alerting you that all's not well.


Well, I also had a diaper analogy, but I decided not to go there.

> > In my case since I do not have a test framework to verify library
> > behavior, perhaps the role of assert is to function as a stopgap until
> > a more format test suite is in place.

>
> * * *... which leads back to the "Should released code be NDEBUG?"
> debate. *Last time around, smart people argued vehemently on both
> sides -- which should tell you something about the maturity of what
> we're pleased to call "software engineering" ...


I think it depends on the amount of trust that you have in the
perceived quality of the library and its test framework. For example,
I'm willing to trust that the gcc implementation of the standard C
library is good from the point that so many people are using it and it
has an extensive test suite. The fact they feel assert (or any
precondition that bombs macro) is not needed in their framework feels
justified imo for those two reasons. Because of those resources,
assert doesn't really buy them anything more than what their test code
base and community already provides.

For the lowly developer who is trying to make something new, I can
definitely see that assert can provide a lot of bang for the buck sort
to speak. If I was using a library that made use of assert, I would
want to include NDEBUG for as long as I perceived that using the
library had an unacceptable level of risk, and I don't mind the
trouble dealing with users when the application program using the
library bombed. The amount is risk in using that library goes down as
the number of users and the quality of the test framework goes up. As
long as the application developer can make that decision by defining
NDEBUG when they compile my library or not, I'm starting to think it's
okay to use assert in a library environment.

Best regards,
John D.

> --
> Eric Sosman
> (E-Mail Removed)


 
Reply With Quote
 
 
 
 
ImpalerCore
Guest
Posts: n/a
 
      05-12-2010
On May 12, 3:19*pm, Gene <(E-Mail Removed)> wrote:
> On Apr 27, 2:25*am, ImpalerCore <(E-Mail Removed)> wrote:
>


[snip]

>
> The two things you're interested in -- library runtime error handling
> and assertions -- are completely different as metaphors and in their
> purposes. *Mixing them will be confusing as a minimum.


Yeah, I have had trouble understanding what the distinctions are.

> Assertions are both documentation and debugging aids that are about
> code invariants (See for example D. Griess _The Science of Computer
> Programming_ on this topic). *At once they document invariants and
> tell you at run time if what you _thought_ was invariant really is
> not; i.e. there is a gap between your mental model of the program and
> what it's actually doing. *An example of an invariant is that
> successive pairs of elements in a sorted list are identically
> ordered. *If you ever find a pair that's out of order, your assertion
> of "sortedness" is obviously wrong.


This example is pretty clear to me.

> Runtime error checks are about assumptions rather than invariants.
> You are checking assumptions about whether the caller provided valid
> arguments, the system has enough memory, that I/O operations
> succeeded, etc. *For these purposes, you want to give the caller lots
> of freedom to deal with the assumption violation in the best possible
> way: report, repair, resume, recover, die gracefully are a few
> options. *Error callbacks are a common approach.


Let me pose my current conundrum. What do you think is the best way
to handle a user passing a null structure to a library function that
uses a member.

For example, I have a c_array_size that takes a 'struct c_array'
pointer. I call c_array_size( NULL ). Which of the following do you
think in your opinion is the best implementation?

1. Do nothing.

\code
size_t c_array_size( struct c_array* array )
{
return array->size;
}
\endcode

2. Assert something.

\code
size_t c_array_size( struct c_array* array )
{
assert( array != NULL );
return array->size;
}
\endcode

3. Have some other error handling.

\code
size_t c_array_size( struct c_array* array )
{
size_t result = 0;

if ( array ) {
result = array->size;
} else {
/* Insert custom error handling here. */
errno = EINVAL;
}

return result;
}
\endcode

How would you classify a user passing a NULL argument to this kind of
library function? An error in an invariant, a library runtime error,
or just the library user being stupid (hence do nothing?). It seems
like it could be one or the other depending on how you look at it.

Best regards,
John D.

> Cheers


 
Reply With Quote
 
Uno
Guest
Posts: n/a
 
      05-14-2010
Keith Thompson wrote:
> Uno <(E-Mail Removed)> writes:
>> pete wrote:
>>> Eric Sosman wrote:
>>>
>>>> ... or in other words, "assert is for developers."
>>> I think of an assert stopping a running program,
>>> as meaning that the program needs more work.
>>>

>> Well, my context has been cut off three times in this thread. First by
>> Ian. I didn't mind that so much. Second by a Rui, who thinks I was
>> advertising a book. Third by Keith, because bandwidth cannot support my
>> comments and his girth.

> [...]
>
> So when you're criticized for excessive quoting, you ignore the
> reasons for the criticism, insult me, and deliberately do it again.
>
> Did it occur to you that your critics might have a valid point?
>
> You might consider rethinking the way you interact with other
> human beings.
>


Keith,

I apologize. I wasn't cut off thrice, but I thought I wasn't getting
through something that I thought was germane.

My critics *almost always* have a valid point, but you have a differing
role among them. I would say that you're the de facto editor in chief
in c.l.c., by virtue of your knowledge, experience, and closeness to the
C development process.

I'm always re-thinking how I deal with people, so you don't have to
worry about that. I was realworld-busy during much of the meaty portion
of this thread; I probably haven't read beyond the 12th message. I
thought it was a quality post, and as first respondent chose to
reproduce the whole post which has been called "absurdly long" by
respondents. I don't see much difference between calling something
"absurdly long" or "just plain fat."

I think OP is still challenging this forum with innovative questions. I
lack the technical prerequisites in C to advise him, but I thought I
would give him some cloudcover.

Cheers,
--
Uno
 
Reply With Quote
 
Uno
Guest
Posts: n/a
 
      05-14-2010
Nick Keighley wrote:

> You are an idiot.
>
>


Prove it.
--
Uno
 
Reply With Quote
 
ImpalerCore
Guest
Posts: n/a
 
      05-14-2010
On May 14, 4:17*am, Uno <(E-Mail Removed)> wrote:
> Nick Keighley wrote:
> > You are an idiot.

>
> Prove it.


I think the point they were trying to make is that the cumulative
amount of work made for other people reading your 2-3 line response
could have been significantly reduced by a little judicial snipping on
your part. Unfortunately that kind of criticism is often flavored
with an assortment of spicy words that don't digest in the best
possible way.

Personally I don't care that much about post edicate. But I still try
to make an effort to format responses, especially since the formatting
seems to have a positive correlation with getting people smarter and
more experienced than me to respond.

Best regards,
John D.

> --
> Uno


 
Reply With Quote
 
Richard Bos
Guest
Posts: n/a
 
      05-15-2010
Uno <(E-Mail Removed)> wrote:
^^^^^^^^^^^^^^^^^^^

> Nick Keighley wrote:
>
> > You are an idiot.

>
> Prove it.


See under^^^^marked bit above.

Richard
 
Reply With Quote
 
Richard Bos
Guest
Posts: n/a
 
      05-17-2010
ImpalerCore <(E-Mail Removed)> wrote:

> On May 11, 10:06=A0am, (E-Mail Removed) (Richard Bos) wrote:
> > ImpalerCore <(E-Mail Removed)> wrote:


> > > I believe that's what he means. =A0Even so, the prototype itself is

> >
> > > char * strcpy ( char * destination, const char * source );

> >
> > > so a library implementation could check whether 'destination' is NULL
> > > and simply return NULL as its return value, yet when I use it, the
> > > library designer decided it was better to have it crash.

> >
> > Most probably he didn't. Instead, he decided that the best thing was to
> > use the default behaviour for your operating system, and the designer of
> > your operating system decided that when a null pointer is dereferenced,
> > your installed null pointer hook is called, but when you aren't running
> > under a debugger there is no such hook installed, so the default handler
> > is used, and _that_ causes a controlled program crash rather than
> > allowing you to scribble all over your code. It is even possible, though
> > I do not know how common, that the OS further defers to the hardware
> > memory handler for scribbling over unallocated memory, and only causes
> > the crash when that level returns with an error.

>
> If I read you correctly, it sounds like nowadays assert is the wrong
> level of abstraction for checking against dereferenced NULL pointers,
> due to operating system maturity.


No; it's more that a library function is the wrong level of abstraction
to use assert() at, unless you're developing and testing that same
library. assert() is good when its output is seen by developers who can
do something about the error. When it's seen (or more likely, not even
seen) by ordinary users, it's useless.

> > > This could be justified by saying that string operations are so low
> > > level that it's worth making the assumption that destination will
> > > never be NULL, but again, why not make assert( destination ) part of
> > > the implementation,

> >
> > Because it causes an extra load for correctly written programs which
> > don't need this assert(), and adds no useful information for the vast
> > majority of users. The time is long, long gone when the average computer
> > luser

>
> I was under the impression that assert was for developers, not
> computer users. My understanding is that assert is NDEBUGed out
> before it gets to the computer user.


The problem is that you don't #include the library. You #include only
the prototypes (plus related material) for the library functions. In
almost all cases, you link in the actual code, already compiled.
Therefore, if you, the user-programmer, #define NDEBUG, that has no
effect at all on the already compiled library functions. A library that
has been compiled with assert()s in place will always have those
assert()s in place, no matter what the program it is linked with does.

Of course, if, as a developer, you would want strcpy() to trigger an
assert() to catch stray null pointers in your own code, a Standard
library with assert() in it can be a useful thing. But your
understanding above is correct: you would want your release build to use
the normal, non-assert() library.

Richard
 
Reply With Quote
 
Richard Bos
Guest
Posts: n/a
 
      05-17-2010
"bart.c" <(E-Mail Removed)> wrote:

> "Eric Sosman" <(E-Mail Removed)> wrote in message


> > One reason that "assert is for developers" is that the output
> > it produces is of use only to the developers. What sense could an
> > end user make of "assertion error: mugwump.c(42) x>0"? If he's C-
> > aware he can guess that an `assert(x>0)' failed, and that it was on
> > line 42 of a source file named mugwump.c, but since he has no copy
> > of mugwump.c, doesn't know what's in the vicinity of line 42, and
> > doesn't even know what `x' signifies, how is he supposed to do
> > anything productive with the information?

>
> He doesn't need to. It can just be part of the report the user sends to the
> developer.


Hah! I want your users. Mine only give me useful reports if I put up a
window containing exact instructions in very un-technical words. If I
print a (to them) cryptic line of techledygook on a console they might
not even have, let alone ever look at, I get sweet Fanny Adams.

Richard
 
Reply With Quote
 
Richard Bos
Guest
Posts: n/a
 
      05-17-2010
ImpalerCore <(E-Mail Removed)> wrote:

> On May 12, 4:21=A0am, Nick Keighley <(E-Mail Removed)>


> > crashing is pretty traditional. The C library assumes you pass it the
> > right stuff. You put the asserts in *your* code.

>
> If the C standard library doesn't populate their library with asserts,
> why should I populate my library with asserts at all?


You should put them in _while you're still developing it_. Once you
start using it - even more so once someone else starts using it - you
compile a NDEBUGged version.
I would not be surprised if someone's version of the Standard library
did exactly that. Of course, unless you have its source code, you'll
never see the difference.

> And the disagreement leaves me with the impression that the assert
> mechanism wasn't designed properly in the first place since no one can
> seem to agree on what exactly it or NDEBUG is intended for.


It might be more balanced to say that, back in the days before the first
Standard, when assert() was designed, the average UNIX user was a lot
closer to being a programmer - and if not, to knowing one personally.

Richard
 
Reply With Quote
 
 
 
Reply

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
List iterator assignment fails, assert iterator not dereferencable David Bilsby C++ 5 10-09-2007 02:05 PM
assert 0, "foo" vs. assert(0, "foo") Thomas Guettler Python 3 02-23-2005 07:53 PM
how to overload an operator to assert object as boolean value ? (not !()) Kai Wu C++ 2 02-16-2005 06:48 AM
assert(x) and '#define ASSERT(x) assert(x)' Alex Vinokur C Programming 5 11-25-2004 08:48 PM
RE: remove assert statement (Was: Re: PEP new assert idiom) Robert Brewer Python 1 11-07-2004 06:53 PM



Advertisments