I
Ian Collins
That's a reasonable objection, but it's one that can easily be fixed up:
assert(is_misbehaving(library) == 0);
assert(is_misbehaving(hardware) == 0);
Define is_misbehaving()!
That's a reasonable objection, but it's one that can easily be fixed up:
assert(is_misbehaving(library) == 0);
assert(is_misbehaving(hardware) == 0);
Ok, let me phrase an assert question on this simple implementation of
strlen.\code
size_t strlen( const char* s )
{
size_t len = 0;while (s[len] != 0)
len++;return len;
}
\endcodeI can write the implementation to do one of the three things.1. Let it bomb as is.2. Put in 'assert( s != NULL );'. When user passes a NULL into it, a
diagnostic message is printed and aborted.3. Check s for NULL, and avoid doing anything that would cause the
function to bomb.\code
size_t strlen( const char* s )
{
size_t len = 0;if ( s )
{
while (s[len] != 0)
len++;
}return len;
}
\endcodeAccording to Ian's rule if I understand it correctly, the first and
only the first implementation should be used, since it's the arguments
are driven specifically by user input.
Who says the argument to strlen() is driven by user input? I see
no user input in the code you posted.
Users don't call strlen(); code does. It's your responsibility
as a programmer to avoid calling strlen() with anything that will
invoke undefined behavior. If you make such a call in response to
use input, it's because you weren't sufficiently careful in your
handling and checking of the user input.
The strlen() function, as the standard defines it, creates an
implicit contract: it will return the length of the string pointed to
by s as long as s actually points to a string. It's the caller's
responsibility to meet that requirement. The user (the person
running your program) isn't a party to the contract.
(Sorry if the above sounds a bit harsh; it wasn't meant that way.)
Many implementations have a built-in automatic assert for null pointer
access anyway; it is called a crash.
assert is for programming errors. With today's computers, malloc will
never return a null pointer.
So you can use this function instead of malloc:
void* unchecked_malloc (size_t size)
{
void* result = malloc (size);
assert (result != NULL || size == 0);
return result;
}
Exactly.
For a great example of how NOT to do this, look at the RPM package
manager, which calls a function which requires root privileges, then
asserts that the call succeeded. Dumb. That's a situation which
requires a diagnostic explaining what went wrong. (I think it first
checks that you have the privileges, but there are other reasons the
call can fail...)
Running rpm on top of pseudo, are we?
Richard Heathfield said:I try to follow that rule myself (although I've never seen it put
quite so eloquently)
, so it's surprising (and perhaps mildly alarming)
just how often my assertions fire during development!
Seebs said:Exactly.
For a great example of how NOT to do this, look at the RPM package manager,
which calls a function which requires root privileges, then asserts that
the call succeeded. Dumb. That's a situation which requires a diagnostic
explaining what went wrong. (I think it first checks that you have the
privileges, but there are other reasons the call can fail...)
From a library perspective,
I consider that there are two kinds of
programming errors. Internal ones within the library, where a user
provides valid input and environment, but due to a logic error I do
something that I detect later because I made an assumption when it
actually was not correct. Then there are the external errors that are
driven by user behavior, whether by giving bad input, or going behind
the API's back and monkeying around, or problems with system
resources.
I don't think I have enough experience to know where to draw that line
yet. Are 'condition that should never happen' considered programming
errors if I detect that it happens?
That's the issue, if I pass an invalid 'pos' argument, from the
library perspective, it doesn't harm anything, as I can silently
ignore it and go about my merry business.
The question is if passing
an invalid 'pos' and silently ignoring it harms the user experience.
My gut is telling me that it is, because the user is likely making a
logical error (overrunning the bounds of the array). If I assert
'(pos < size)' from the library, it's a message to the library user
that you're violating the contract to gc_array_erase, you *better* do
something about it.
I can accomplish the same thing using return
codes or a 'errno = EINVAL' mechanism as well,
which is telling the
user that *maybe* you should do something about it, and then place the
burden on the user; I just can't quite make the case convincing enough
that using an assert in this scenario is 'The Wrong Thing To Do'.
I agree completely. I'm just trying to grasp what the last resort's
are. Thanks for your comments.
Plauger's library book used assert()s in his test harness. A not bad
idea I think.
I consider there are two types of error.
Logic Errors
Runtime Errors
logic errors can, in principle (by the Sufficiently Smart Programmer),
be detected purely by examining the program text. With no idea of the
input or the execution environment.
runtime errors can only be detected (as the name implies) at runtime.
Runtime errors are caused by improperly validated user input or other
runtime input. Resource exhastion (run out of memory or file handles)
or failure of hardware, comms or databases.
Assert()s are for logic errors. In an ideal world thourough validation
(testing, walkthrus etc.) mean that shipped code shouldn't have any
logic errors. Some peopel therefor turn off assert() indelivered code.
Other less perfect programmers leave at least some of the asserts in
on the grounds that detecting an error is better than blundering on.
look up Design by Contract. Each function has pre-conditions ("the
count must be greater than zero") which muct be met. In return the
function meets a post-condition ("returns the mean of values in the
array" "the control channel will be set to the new value"). The pre-
conditions in particular can often be enforced by assert()s (post-
conditions tend to be more expensive to compute).
I'll often have "user facing" code do heavy validation and internal
functions rely on assert()s (the parameters should all have been
validated already). I use a "how foreign are they" as my rule of thumb
as what is "user" facing. I trust the code from the guy at the next
desk more than I trust the code from the team in the next building.
Everything that comes over a comms like is potentially crap.
maybe.
PRE-CONDITION (sum-of-active-calls() > maximum-number-of-calls)
should never happen. But what should you do? Assert probably as some
fundamental assumption on which the program is based has been violated
(other bits of code might be using this to calculate how many free
calls there are left- this being negative only boggles the mind). Your
code just isn't ready for the wild.
Other places you might be able to run on after discarding the current
transaction. Drop a call, discard a packet, roll back a partial
database transaction. You've faile dto process something but the
datbase is still intact and other users are getting their stuff done.
<snip>
you need to decide exactly what "array_erase()" means.
not keen in general on this. Might the caller think that he *has*
erased something. Silently ignoring user requests generally seems bad.
Deleting non-exitent array elements generally seems not array-like
behaviour.
yes, Take a look at exceptions. Either C++ where I enountered 'em or a
slightly less rough ride, learn python.
errno is pretty generally agreed to be broken
it depends on the semantics of your array. I'd be pretty brutal (ie
assert) in such a low level class. I'd expect higher level
abstractions to be gentler on the caller.
gc_array_erase- asserts on array bound errors
remove-basestation- returns ITEM_NOT_FOUND if there is no such
basestation
I make no claim to internal consistency
your call. Since the function is called "...insert_sorted" I don't
think it would be unreasonable to expect the array to be sorted.
Perhaps provide a user callable predicate
is_array_sorted (const c_array_t*) that returns TRUE if the array is
sorted. Then the user can avoid making dumb mistakes.
well I've tried to help, so I'm sure you're thoughly confused!
Do you need a PIN number to use the RPM package number on your
personal PC computer? ;-)
So they're _releasing_ the code with assert()s enabled?
It's a good example to think about for an assert, though. You make a
call out to the underlying operating system or implementation. Should
you assert() that the operation succeeded? OF COURSE NOT. That would
be ridiculous.
If an operation has a way to indicate that it has failed, it is almost
certainly expected to be POSSIBLE for it to fail. Therefore, you should
not use an assert() to check for it, because that's not a safe
assertion.
The intent of an assertion should be that you are quite sure that, short
of someone flipping bits randomly in memory, it will never fire. IMHO.
So they're _releasing_ the code with assert()s enabled?
Bodgers. Remember kids - don't do RPM.
[snip stuff I generally agree with]
Some peopel therefor turn off assert() indelivered code.
... For example, see sigprocmask() [0]. (Of course, that's not
file-manipulation related, so it likely isn't relevant for pseudo.)
The
only "shall fail" condition occurs iff the programmer makes a mistake
calling sigprocmask(). There is no "may fail" condition. Assuming one
manages to pass one of SIG_BLOCK, SIG_SETMASK and SIG_UNBLOCK for /how/, I
think an assert(0 == retval) wouldn't be unreasonable. Especially because
a failing sigprocmask() has both of these characteristics:
- it can break the program horribly,
- it really cannot be worked around or fixed or ignored.
Assertions in the context of a test harness are generally notI agree. I'm not at the point where I have a test harness yet, but I
do want to build one. And asserts would be part of the test harness.
Assertions in the context of a test harness are generally not
implemented with assert(). If they were, you could only detect one test
failure in a run!
Test assertions indicate failure by other means.
ImpalerCore said: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.
Assertions in the context of a test harness are generally not
implemented with assert(). If they were, you could only detect one test
failure in a run!
Test assertions indicate failure by other means.
The typical usage pattern (I know this function isn't standard C, but
the usage pattern is adequately generic) is:
sigprocmask(SIG_BLOCK, &new_signals, &old_values);
/* do stuff */
sigprocmask(SIG_SETMASK, &old_values, NULL);
Now, if sigprocmask fails, you probably don't want to /* do stuff */.
But you might want to have the *entire operation* return a meaningful
failure, possibly with a diagnostic. For instance, let's say that we
use this idiom when about to save a data file. Something goes wrong, we
don't know what, and sigprocmask fails unexpectedly. We could abort()
at this point... But that seems unfriendly! More friendly would be to
report that the save operation failed, and let the user try to do
something else to avoid losing the data.
So even though I can't recover from the failure, in that I can't
complete some operation, I can do something more graceful than
coredumping -- I can report a meaningful failure and *not try to do
that*.
Someone passes a null string to strcpy. As the designer of thisSometimes there's nothing else to do. But I agree, being friendly is good..
Want to reply to this thread or ask your own question?
You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.