Velocity Reviews - Computer Hardware Reviews

Velocity Reviews > Newsgroups > Programming > C Programming > Mechanism to generate annotated "error codes"

Reply
Thread Tools

Mechanism to generate annotated "error codes"

 
 
Keith Thompson
Guest
Posts: n/a
 
      03-02-2012
Ian Collins <(E-Mail Removed)> writes:
> On 03/ 2/12 11:05 PM, Keith Thompson wrote:
>> Don Y<(E-Mail Removed)> writes:
>> [...]
>>> You need to maintain this "by hand". There is nothing that
>>> prevents you from:
>>> #define YES (2)
>>> #define NO (2)
>>>
>>> Nor:
>>> #define YES (2)
>>> ...
>>> #define ABSOLUTELY (27)
>>> (assuming yes and absolutely are synonyms)

>> [...]
>>
>> A minor point: parenthesizing macro definitions is a good habit, but
>> it's not necessary when the macro expands to a single token. This:
>>
>> #define ABSOLUTELY 27
>>
>> is just as safe as
>>
>> #define ABSOLUTELY (27)

>
> Or simply don't use macros for constants!


27!. Um, I mean ABSOLUTELY! }

But note that declaring:

const int ABSOLUTELY = 27;

gives you something that you can't use where a constant expression
is required, nor can you meaningfully use it in a #if directive.
For values in the range of int, you can work around that by using
an enum:

enum { ABSOLUTELY = 27 };

(In either case, you probably wouldn't use all-caps for the name.)

--
Keith Thompson (The_Other_Keith) http://www.velocityreviews.com/forums/(E-Mail Removed) <http://www.ghoti.net/~kst>
Will write code for food.
"We must do something. This is something. Therefore, we must do this."
-- Antony Jay and Jonathan Lynn, "Yes Minister"
 
Reply With Quote
 
 
 
 
Don Y
Guest
Posts: n/a
 
      03-03-2012
Hi Kaz,

On 3/2/2012 3:57 PM, Kaz Kylheku wrote:
> On 2012-03-02, Don Y<(E-Mail Removed)> wrote:
>> Hi Keith,
>>
>> On 3/2/2012 3:05 AM, Keith Thompson wrote:
>>
>>> A minor point: parenthesizing macro definitions is a good habit, but
>>> it's not necessary when the macro expands to a single token. This:

>>
>> Keeping a gun's safety "on" is "a good habit". Do you often
>> leave it *off* just because you don't *think* you need it
>> "on" at the present time? Or, do you wait until you
>> really *need* it "off" (i.e., just prior to discharge)?

>
> Do you often modify your gun to a completely different model,
> and without touching the safety switch?


Is there NO possibility of someone OTHER THAN YOU having access
to that weapon? Someone who doesn't have the same sense of
discipline that you *might*?

You would adopt the same HABIT with the new weapon. Habits are
things that you shouldn't have to PAUSE AND THINK ABOUT. Do
you *consciously* think about the semicolon at the end of each
source statement? Do you *consciously* think about the parens
following a function invocation?

E.g., I only consciously think about semicolons on empty
statements. And, since these are so atypical, it gives me
reason to pause and DRAW ATTENTION to these "/* EMPTY */"
statements.

You shouldn't have to ask yourself, "Should I put the safety
on, now?". You should do it without thinking.

"Should I parenthesize this definition?"

The "stop and think" action should be the exception leading
to the more "risky" behavior: "Do I *really* want to be ready
to KILL someone?"

"Is there some reason why I *shouldn't* parenthesize?"

Establish behaviors that tend to decrease the chances of
people making undetected (or, expensive to detect) mistakes.
The compiler will complain if you add parens somewhere that
violates syntactic requirements. It *won't* if you fail to
add the parens. (yes, a friendly compiler can warn you
of this possibility -- but, doesn't *have* to)

IME, people mimic the styles and practices of the code
they are maintaining. You play fast and loose with how
you write things and you invite others to do the same.

You will note that the solution I am using from the original
post (Hmmm... original post? What's *that*?!) did not expose
#defines to the developer. It intentionally hid the "how's"
of the mechanism from the developer while providing the
desired functionality. The MakeErrorCode mechanism can be
implemented any way I choose -- so long as the code it
generates (none) is predictable and the functionality desired
is present. E.g., all I need to do is make sure the compiler
(when *it* sees the code "for real") knows how to resolve
"Err_TooManyDecimalPoints" -- whether as a manifest constant,
extern variable, a substitution performed by the preprocessor
(or other agent), etc.

Build a mechanism that is easy for others to adopt so that
it is easier for them to continue that behavior than to
come up with a different, ad hoc approach on their own.
(e.g., the *expensive* mechanism of storing error messages
in a real database instead of ad hoc strings littered through
the code and ad hoc mechanisms for presenting them to the user.)

Of course, if you're the only one maintaining your code
or can be assured people AT LEAST as competent as you
are the only ones that it is exposed to, you can get away
with whatever you want. I find that in heavily regulated
industries and large projects (100's of KLoC's), folks seem
to want to err on the side of more "consistency" than "less".
 
Reply With Quote
 
 
 
 
Don Y
Guest
Posts: n/a
 
      03-03-2012
Hi Ian,

On 3/2/2012 4:03 PM, Ian Collins wrote:
> On 03/ 2/12 11:05 PM, Keith Thompson wrote:
>> Don Y<(E-Mail Removed)> writes:


>>> You need to maintain this "by hand". There is nothing that
>>> prevents you from:
>>> #define YES (2)
>>> #define NO (2)
>>>
>>> Nor:
>>> #define YES (2)
>>> ...
>>> #define ABSOLUTELY (27)
>>> (assuming yes and absolutely are synonyms)

>> [...]
>>
>> A minor point: parenthesizing macro definitions is a good habit, but
>> it's not necessary when the macro expands to a single token. This:
>>
>> #define ABSOLUTELY 27
>>
>> is just as safe as
>>
>> #define ABSOLUTELY (27)

>
> Or simply don't use macros for constants!


Or, as described in my "solution", *hide* all of that mechanism from
the developer! *Don't* build large tables MANUALLY with bogus
constants in them. Let the machine do what it does best -- simple,
repetitive tasks (like expanding MakeErrorCode into something
useful for the functionality it provides).
 
Reply With Quote
 
Kaz Kylheku
Guest
Posts: n/a
 
      03-03-2012
On 2012-03-03, Don Y <(E-Mail Removed)> wrote:
> Hi Kaz,
>
> On 3/2/2012 3:57 PM, Kaz Kylheku wrote:
>> On 2012-03-02, Don Y<(E-Mail Removed)> wrote:
>>> Hi Keith,
>>>
>>> On 3/2/2012 3:05 AM, Keith Thompson wrote:
>>>
>>>> A minor point: parenthesizing macro definitions is a good habit, but
>>>> it's not necessary when the macro expands to a single token. This:
>>>
>>> Keeping a gun's safety "on" is "a good habit". Do you often
>>> leave it *off* just because you don't *think* you need it
>>> "on" at the present time? Or, do you wait until you
>>> really *need* it "off" (i.e., just prior to discharge)?

>>
>> Do you often modify your gun to a completely different model,
>> and without touching the safety switch?

>
> Is there NO possibility of someone OTHER THAN YOU having access
> to that weapon? Someone who doesn't have the same sense of
> discipline that you *might*?


Since #def SYM 27 is perfectly safe when accessed, this analogy falls
flat on its face, too.

> You would adopt the same HABIT with the new weapon. Habits are
> things that you shouldn't have to PAUSE AND THINK ABOUT.


Someone else's good habit is not writing parentheses around single tokens.

It's only a tiny bit more complex than the naive habit of always doing it, but
it's still a habit.

In a car, you have two pedals operated with the same foot that do something
quite opposite, yet your brain can generally work out which to use.
You don't think, "I want to slow down; is it the left one?"
 
Reply With Quote
 
Stefan Ram
Guest
Posts: n/a
 
      03-03-2012
Don Y <(E-Mail Removed)> writes:
>You need to maintain this "by hand". There is nothing that
>prevents you from:
> #define YES (2)
> #define NO (2)


The include-file can be generated:

printf( "#define YES (%d)\n", ++i );

 
Reply With Quote
 
Malcolm McLean
Guest
Posts: n/a
 
      03-03-2012
There's no good answer.

The first thing to do is to divide your code into "pure functions" and
"procedures". A pure function shuffles bits, a prodeucre shuffles bits
and does IO.
A pure function can only fail in three circumstances, it's called with
invalid parameters, there's an internal error in its coding, or it
runs out of memory.
A procedure can fail in these circumstances, and also because of a
hardware problem, because the user provides erroneous or even
malicious data, because hardware is functioning but is overwhelmed by
the demands of the prodecure, or because of missing resources such as
non-existent files.

So let's take the situations one by one. If you have invalid
parameters, that normally indicates a programming error in calling
code. The rare exception is when caller can't reasonably be expected
to check the parameters for validity - e.g. a statistical procedure
might fail when the numbers have a distribution that becomes less like
a bell curve when you take means of a sample. Caller can't reasonably
be expected to check for that condition. If caller can't be expected
to check the parameters, an "abormal" result must be passed back as
part of the normal flow control of the program. If caller can, there's
not much point in shuting the error back up to a buggy caller. You
need to abort the program with an error message if it can be aborted,
suppress the error if abortion isn't an option.

Internal error - difficult. Your own code is buggy. There's no real
answer to this situation.

Out of memory - if it can reasonably be expected that the function
will run out of memory, shunt up an "out of memory" condition to
caller. If basically this can't happen (you need to store one filename
dynmaically ona amchine with 4GB of memory), abort with an out of
memory message.

Hardware problems: is in once in a blue moon, or can it reasonably be
expected? If it's once in a blue moon, simply report an IO error and,
usually, terminate. If it's expected, you will need to know how to
code round the expected harware failures.

Bad user data - usually you should assume that the user is a hacker
trying to make your program malfunction. Should be normal control path
of the program, and reported up to caller.

Overhelmed hardware - very difficult. If it will take a whole day to
write results to disk, is this acceptable or must be abort? It's often
not easy to answert these questions, or anticipate them. The file
might be quite small, the disk hardware very busy.

Missing resources - happens all the time. Treat as normal flow control
of program.

So generally the strategy is to treat so-called error conditions as
normal flow control, and pass the error condition up to caller. So
they're not really errors at all. The exception is errors caused by
programming mistakes. It's dangerous to pass an error back up to a
buggy caller. Normally you want to assert fail, which gives the
compiler the chice between reporting and aborting, or siliently
suppressing the error.

Centralised error systems mean that code will break if you try to move
it to a different project.

--
Basic Algorithms - massive compendioum of C programming resources
http://www.malcolmmclean.site11.com/www

 
Reply With Quote
 
Don Y
Guest
Posts: n/a
 
      03-03-2012
Hi Malcolm,

On 3/2/2012 11:13 PM, Malcolm McLean wrote:
> There's no good answer.
>
> The first thing to do is to divide your code into "pure functions" and
> "procedures". A pure function shuffles bits, a prodeucre shuffles bits
> and does IO.


So, your classification treats a parser as a "pure function" -- if
the I/O has been handled outside of the scope of the parser.

> A pure function can only fail in three circumstances, it's called with
> invalid parameters, there's an internal error in its coding, or it
> runs out of memory.


In my case, a "process" (being imprecise in my terms) can also
*lose* a resource (e.g., memory) that it previously possessed.
But, its failure to act properly on the notification of that loss
would fall under the "bug" category (though there are some
degenerate cases where a resource may be withdrawn before the
process can act on the notification -- but the OS can deal with
that special case)

> A procedure can fail in these circumstances, and also because of a
> hardware problem, because the user provides erroneous or even
> malicious data, because hardware is functioning but is overwhelmed by
> the demands of the prodecure, or because of missing resources such as
> non-existent files.


How are you differentiating the parser instance from the "malicious
data" instance? What if the data to be parsed originally came from
user I/O? What if it came from a corrupted data store? (is that
a "hardware failure"?)

> So let's take the situations one by one. If you have invalid
> parameters, that normally indicates a programming error in calling
> code. The rare exception is when caller can't reasonably be expected
> to check the parameters for validity - e.g. a statistical procedure
> might fail when the numbers have a distribution that becomes less like
> a bell curve when you take means of a sample. Caller can't reasonably
> be expected to check for that condition. If caller can't be expected
> to check the parameters, an "abormal" result must be passed back as
> part of the normal flow control of the program. If caller can, there's
> not much point in shuting the error back up to a buggy caller. You
> need to abort the program with an error message if it can be aborted,
> suppress the error if abortion isn't an option.


Parsing data provided (as an "input" to your pure function)
could nominally *expect* to find problems in that data through
no fault of the caller or the function operating on it. Yet,
the intent of moving the parsing into this "pure function"
is so the caller need not be aware of the requirements the
parser imposes on the data.

I.e., this looks like "bad data/parameters" but isn't a failure
of the code from either party. Since the parser didn't do the
I/O that originated the bad data, ...

You may not be able to communicate with the originator of that
data (e.g., if you read it from a file ... or, a const array
embedded somewhere in your image). And, you may not want to abort
the "program" -- just that *aspect* of the processing.

> Internal error - difficult. Your own code is buggy. There's no real
> answer to this situation.


It depends on the nature of your error (as do hardware errors!).
Your code might still be able to reliably report an error. It
just might not make sense to you -- until you examine your
code and see why your code is "deliberately" making that mistake.

I.e., if the results aren't safety critical, you make a best
effort and hope for the best. (I have some systems where
"/* CAN'T HAPPEN */" really should NEVER happen and, as a
result, you want to lock up the processor *hard* so that
it doesn't do anything potentially dangerous or lossy)

> Out of memory - if it can reasonably be expected that the function
> will run out of memory, shunt up an "out of memory" condition to
> caller. If basically this can't happen (you need to store one filename
> dynmaically ona amchine with 4GB of memory), abort with an out of
> memory message.


But the user doesn't want to see "out of memory". The user doesn;t
need to *know* what's going on inside the code. The memory error
needs to be reinterpreted in the context of higher levels in the
application and expressed in a way that is appropriate to that
application and user base. This allows lower level libraries to
be reused between applications and *within* an application.

Running out of memory when trying to add a name to an address
book should yield a different message than when trying to print
a banner. The role of the memory allocation -- along with its
persistence and remedies -- varies based on that context.

I.e., "Your address book is full. Delete one or more entries
of you want to add this new contact" vs. "Image too large to
print. Try a lower resolution or size." Putting the "out of
memory" error into context adds value to the user (and thus
the product).

> Hardware problems: is in once in a blue moon, or can it reasonably be
> expected? If it's once in a blue moon, simply report an IO error and,
> usually, terminate. If it's expected, you will need to know how to
> code round the expected harware failures.
>
> Bad user data - usually you should assume that the user is a hacker
> trying to make your program malfunction. Should be normal control path
> of the program, and reported up to caller.


[Again, how does this tie in with the parser "pure function"?]

IMO, you want to provide as much information to the user to enable
them to correct their entry (even if it is a malicious user). This
is where exposing internal "errors" from a lower level function
(e.g., parser) can benefit the user without burdening the application
or tying low level routines to a particular application.

For example, complaining that an integer value contained a decimal
point can be reported to the user regardless of whether the upper
layer that invoked the parsing routine was using it to fetch an
*age*, IQ, ZIP code, etc. The context associated with the upper
layer gives the "problem report" ("error" is a hard word to
avoid) some meaning; further qualification obtained from the
report of the lower level parser helps to clarify that:

Upper context: "The age that you entered is invalid."
Lower context: "The value must be a whole number -- it can not
contain a decimal point"

> Overhelmed hardware - very difficult. If it will take a whole day to
> write results to disk, is this acceptable or must be abort? It's often
> not easy to answert these questions, or anticipate them. The file
> might be quite small, the disk hardware very busy.
>
> Missing resources - happens all the time. Treat as normal flow control
> of program.
>
> So generally the strategy is to treat so-called error conditions as
> normal flow control, and pass the error condition up to caller. So
> they're not really errors at all. The exception is errors caused by
> programming mistakes. It's dangerous to pass an error back up to a
> buggy caller. Normally you want to assert fail, which gives the
> compiler the chice between reporting and aborting, or siliently
> suppressing the error.


If the assertion is going to just terminate the program, then
the user has learned nothing. He reruns the program and it
dies, again.

> Centralised error systems mean that code will break if you try to move
> it to a different project.


I don't see that as a consequence of this sort of approach.
My scheme pieces errors (and descriptions) together as they
are encountered in the executable. Out_Of_Memory might resolve
to 0x2345678 today, 0x107 tomorrow and NEVER HAPPEN some time
next week. (i.e., grep the sources and you never FIND that
string in it!)

Furthermore, *your* instance of the sources might have a
different set of errors (and, thus, codes) than *my* image
based on the types of errors that our respective assignments
require us to detect/encounter.

And, when our images are merged, we magically end up with the
union of error messages and "codes" that we have individually
used.

As I said, I already have a version of this working -- borrowing
algorithms from my IDL compiler (same issues -- automatically
managing "message types"). But, it is hard to tell make(1)
when the error codes might have changed. So, any change to
a source file requires rebuilding the error code catalog.
The only way I can currently get make to cooperate is if I
introduce an intermediate file that I only touch(1) if the
error codes need to be updated. Then, put a separate dependency
on that file that triggers the building of the error codes
for other files. As projects get bigger, this approach doesn't
scale well :< (or, it coerces you into doing things in
less than optimal ways *just* to workaround the tools)

[I also have a problem using __FILE__, __func__ and __LINE__ as
components of unique identifiers as I might have 20 files named
get.c. Each with a function called get() within. And the
structure of each get() highly resembling the other 19 get()'s
(i.e., signaling errors on the same __LINE__s though physically
different files). And, I place strong restrictions on what can
be done with these "error codes" -- they aren't "regular numbers"]

If you try to hard-code error "codes", then you ARE stuck with a
system that only works for *you* and *your* application. Not a
very smart move, IMO.

Barring some other clever approach, I will just write a utility to
build the error codes and add it into the build process -- using
the IDL compiler as a rough framework (same sorts of issues). I
don't want to head down that path until I know that's the *best*
option!
 
Reply With Quote
 
James Kuyper
Guest
Posts: n/a
 
      03-03-2012
On 03/02/2012 07:14 PM, Don Y wrote:
> Hi Kaz,
>
> On 3/2/2012 3:57 PM, Kaz Kylheku wrote:
>> On 2012-03-02, Don Y<(E-Mail Removed)> wrote:
>>> Hi Keith,
>>>
>>> On 3/2/2012 3:05 AM, Keith Thompson wrote:
>>>
>>>> A minor point: parenthesizing macro definitions is a good habit, but
>>>> it's not necessary when the macro expands to a single token. This:

....
> "Is there some reason why I *shouldn't* parenthesize?"


Well, Ben Pfaff has provided one example where parenthesizing a macro
definition interferes with it's intended usage. A parenthesized string
literal is not a string literal, and will therefore not be merged with
adjacent string literals. Many string literal macros are intended to be
used in the construction of larger string literals, so parenthesizing
them would interfere with that use case.
--
James Kuyper
 
Reply With Quote
 
James Kuyper
Guest
Posts: n/a
 
      03-03-2012
On 03/03/2012 05:30 AM, BartC wrote:
> "Keith Thompson" <(E-Mail Removed)> wrote in message
> news:(E-Mail Removed)...

....
>> A minor point: parenthesizing macro definitions is a good habit, but
>> it's not necessary when the macro expands to a single token. This:
>>
>> #define ABSOLUTELY 27
>>
>> is just as safe as
>>
>> #define ABSOLUTELY (27)

>
> What happens when someone experiments with the value 27 by trying this:
>
> #define ABSOLUTELY 27+1


The same thing that happens when somebody experiments as follows:

#define ABSOLUTELY (27)+1

Or

#define ABSOLUTELY (27)+(1)

If someone doesn't understand why the parenthesis are unnecessary for
(27), then that person can't be counted on to understand whey they are
necessary in (27+1). For such a person, it may be best to parenthesize
everything, just to be safe - the compiler will almost certainly
complain if someone attempts string literal merging with a parenthesized
string literal. I think it's preferable, however, to gain the small
amount of additional understanding needed to realize why (27) is in fact
unnecessary, and ("Absolutely") may actually be a problem.
--
James Kuyper
 
Reply With Quote
 
Malcolm McLean
Guest
Posts: n/a
 
      03-03-2012
On Mar 3, 7:49*am, Don Y <(E-Mail Removed)> wrote:
> Hi Malcolm,
>
> On 3/2/2012 11:13 PM, Malcolm McLean wrote:
>
> > There's no good answer.

>
> > The first thing to do is to divide your code into "pure functions" and
> > "procedures". A pure function shuffles bits, a prodeucre shuffles bits
> > and does IO.

>
> So, your classification treats a parser as a "pure function" -- if
> the I/O has been handled outside of the scope of the parser.
>

A parser is a pure function (assuming IO is not handled by the
function). It takes one sequence of bits - the script - and it returns
a another sequence of bits - the result. So all it's doing is
shuffling bits about in memory. If a pure function calls a procedure,
it's no longer a pure function.

However most parsers will do IO. That leads to a question. If a pure
function takes a function pointer as a parameter, and that parameter
is to a procedure, is it still a pure function? The answer is no, but
here the classification is beginning to lose its usefulness.

Parsers are an obvious case of invalid input not being reasonably
caught by caller. You could require that caller pass only correct
scripts to the parser, but that means caller has to do almost as much
work as the parser itself to verify that the script is correct. So you
have to treat malformed scripts as part of the normal flow control of
the parser. The caler's then got the problem. If the script was typed
into the source byt he programer, it's an internal error. If the
script was provided by outside, it should usually be treated as normal
flow control - you expect complex user-provided scripts to be
malformed.
 
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
A portable LISP interpreter that includes all the majorlist-processing functions is described. A complete, annotated listing of theprogram's code, written in PASCAL, is included. Emmy Noether C Programming 5 08-05-2010 04:31 PM
A portable LISP interpreter that includes all the majorlist-processing functions is described. A complete, annotated listing of theprogram's code, written in PASCAL, is included. Emmy Noether Python 6 08-05-2010 04:31 PM
Can decorator syntax do this ? (annotated results' names) Sakesun Roykiattisak Python 1 08-05-2004 02:06 PM
The Annotated C++ Language Standard by Koenig & Stroustrup???? Steven T. Hatton C++ 6 04-12-2004 06:05 AM



Advertisments