"mike3" <
[email protected]> wrote in message
I think you've designed in too much error-checking, and made your bignums
unwieldy to initialise and manage.
Any code trying to actually do something with bignums would be lost amongst
all the error-checking and recovery. And if there was some conditional code,
and every single calculation needed checking, then you would easily lose
track of what needed freeing and what didn't.
Why does an initialisation need error-checking anyway? Does it involve
allocating memory?
Yes. You have to allocate memory to initialize (for the buffer holding
the
bignum's digits.). And that can fail. Thus, I return an error code.
It's not clear if a bignum is a pointer or a struct; they could be simply
initialised to NULL or to {0}, leaving it to the bignum arithmetic routines
to allocate as needed, and do the error-checking. Your code might still need
to free, but the free routine can leave alone bignums that are still NULLor
{0}.
Actually, for memory management, you should take seriously the idea put
forward of using garbage collection. Then you don't need to explicitly free
memory.
(As a personal preference, I would use something like BN_Init(). Then these
big names would dominate the code so much.)
And, whatever the initialisation does, is it really necessary to check the
result? Why not leave it to routines such as BigNum_Set() to validate it's
arguments; if an argument has not been properly initialised, then *it*
returns a failure code.
What do you do with that failure code?
See, you still need a
handler around
every math routine call. This would help with the Init() functions'
handlers, though.
And what is likely to go wrong with BigNum_Free() that will need checking?
Will the caller of this function care, provided it gets the right answer?Or
is the result of BigNum_Set() likely to be invalidated if a subsequent free
fails?
Looking at the free function, it can return one error code:
ERROR_INVALID_STORAGE_TYPE. A bignum can be stored on either
memory or on disk (disk not yet implemented, but the stuff is there to
allow
for its implementation). There is, of course, a field in the bignum
that indicates
which is being used. If this is something weird -- something other
than
"memory" or "disk", then the "Free" function would get confused as it
wouldn't know how to properly free the bignum. Normally, that
shouldn't
happen. But if someone was doing something silly, the catch is there
to catch that. So, should I just remove that error return and say,
"well,
if you're messing around with this in some improper fashion, then
expect things to break"? But then we run into the problem of where
the field is corrupted due to a memory bug. In this case, a fail could
be useful (although one of the other bignum routines could puke as
well.).
An idea I had is to drop the type field altogether and use
something like this:
typedef struct BigNum {
....
Digit *RAMBuffer;
FILE *DiskBuffer;
....
} BigNum;
and when BigNum is initialized to RAM storage, RAMBuffer holds a ptr,
and DiskBuffer is NULL, and when BigNum is initialized to Disk
storage,
RAMBuffer is NULL, and DiskBuffer holds a ptr. Then there's no field
that
can get an invalid value, and if someone tries to set the NULL ptrs
to something else, then expect things to break. (If a ptr gets
corrupted,
you're pretty screwed anyway and there isn't much you can do about it)
If you do need to do this, then just combine all the statuses of multiple
calls to BigNum_Free() (the error code needs to be of a format that will
allow | or & operations).
The thing here is, that I have a general "universal" list of error
codes, numbered
0 (= success), 1, 2, 3, etc. and functions take their return codes
from that.
Perhaps that's not the best way to do it? (I was inspired by
Microsoft's Windows
system with all its error codes.) E.g. we could use for the bignum
routines
a special set of 32-bit (or even 64-bit, in this case -- this program
is for 64-bit
systems anyway) error codes with at most 32 possible errors (probably
way more than'd be needed from a simple bignum routine!) error codes
that
indicate errors by bit flags (all zeroes = success), and could be ORed
together
to get what errors occurred in the code under consideration.
I know your post is about managing error recovery, rather than the meritsof
a particular library. But, someone needing to do arithmetic via function
calls, might prefer to keep error-checking low-key, perhaps something like
this:
ErrorCode BigNum_Average(BigNum A, BigNum B, BigNum Result) {
BigNum Two=NULL;
ErrorCode e=0;
e |= BigNum_Add(A,B, Result);
e |= BigNum_Set(&Two, 2);
e |= BigNum_Div(Result, Two, Result);
e |= BigNum_Free(&Two);
return e;
}
Then it is still reasonably easy to follow what's going on. (Actually, I
wouldn't bother with the error codes myself; I would build a status into the
bignum itself, a bit like the Nans of floating point.)
Yes, a "NAN" or error flag is another possibility. Flag would be best
-- it's
real easy to check and you could set multiple values -- like 1 for bad
alloc,
2 for overflow, etc.