Error returns and repeated code, GOTOs, etc.

I

ImpalerCore

Hi.

I've got some stuff that looks like this:

---
ErrorCode MyFunc()
{
          BigNum a, b, c, d;
          ErrorCode rv;

          rv = BigNum_Initialize(&a);
          if(rv != ERROR_SUCCESS)
            return(rv);

          rv = BigNum_Initialize(&b);
          if(rv != ERROR_SUCCESS)
          {
            BigNum_Free(&a); /* and what if THIS fails??? :) */
            return(rv);
          }

          rv = BigNum_Initialize(&c);
          if(rv != ERROR_SUCCESS)
          {
            BigNum_Free(&b);
            BigNum_Free(&a);
            return(rv);
          }

          rv = BigNum_Initialize(&d);
          if(rv != ERROR_SUCCESS)
          {
            BigNum_Free(&c);
            BigNum_Free(&b);
            BigNum_Free(&a);
            return(rv);
          }

          /* do some math */
          rv = BigNum_Set(&b, 3);
          if(rv != ERROR_SUCCESS)
          {
            BigNum_Free(&d);
            BigNum_Free(&c);
            BigNum_Free(&b);
            BigNum_Free(&a);
            return(rv);
          }

          ...

          /* free buffers */
          rv = BigNum_Free(&d);
          if(rv != ERROR_SUCCESS)
          {
            BigNum_Free(&c);
            BigNum_Free(&b);
            BigNum_Free(&a);
            return(rv);
          }

          rv = BigNum_Free(&c);
          if(rv != ERROR_SUCCESS)
          {
            BigNum_Free(&b);
            BigNum_Free(&a);
            return(rv);
          }

          rv = BigNum_Free(&b);
          if(rv != ERROR_SUCCESS)
          {
            BigNum_Free(&a);
            return(rv);
          }

          rv = BigNum_Free(&a);
          if(rv != ERROR_SUCCESS)
            return(rv);

          return(ERROR_SUCCESS);}

---

ErrorCode MyFunc()
{
BigNum a, b, c, d;
ErrorCode rv;

rv = BigNum_Initialize(&a);
rv = rv == ERROR_SUCCESS ? BigNum_Initialize(&b) : rv;
rv = rv == ERROR_SUCCESS ? BigNum_Initialize(&c) : rv;
rv = rv == ERROR_SUCCESS ? BigNum_Initialize(&d) : rv;

if (rv != ERROR_SUCCESS)
{
BigNum_Free(&d);
BigNum_Free(&c);
BigNum_Free(&b);
BigNum_Free(&a);
}

return(rv);
}
\endcode

The main issue with this style is that it assumes that BigNum_Free
operates like 'free' where a NULL pointer is a no-op. If you can
initialize your BigNum variables to a corresponding "NULL" BigNum
state, and BigNum_Free interprets a "NULL" BigNum as a no-op, you can
duplicate this style. You may need to add a distinct struct
initializer to your variable declarations if BigNum is not a pointer.

\code
/* Define an initialize macro to create a "NULL" BigNum */
#define BIGNUM_NULL_INIT { 0, NULL, ... }

BigNum a = BIGNUM_NULL_INIT;
BigNum b = BIGNUM_NULL_INIT;
BigNum c = BIGNUM_NULL_INIT;
BigNum d = BIGNUM_NULL_INIT;
\endcode

On a side note, the term ERROR_SUCCESS appears contradictory to me. I
would prefer something like NO_ERROR or SUCCESS if you had control
over the error definitions.

Best regards,
John D.
 
B

Ben Bacarisse

mike3 said:
I've got some stuff that looks like this:
<snip code>

Just because no one has yet used an array:

ErrorCode MyFunc(void)
{
enum { a, b, c, d, n_big_nums };
BigNum num[n_big_nums];
ErrorCode rv;

int n = 0;
while (n < n_big_nums && BigNum_Initialize(&num[n]) == ERROR_SUCCESS)
n++;

if (n == n_big_nums)
do {
/* do some math */
rv = BigNum_Set(&num, 3);
if (rv != ERROR_SUCCESS)
break;
/* ... */
} while (0);

while (n--)
BigNum_Free(&num[n]);
return rv;
}

You don't have to use the do { ... } while (0); thing. Just add a label
to the end loop and goto it instead of using break.

You have to switch a, b and so on to num[a], num but it does extend
easily if you find you need another BigNum.

(Note that "ErrorCode MyFunc(void)" is a prototype whereas "ErrorCode
MyFunc()" is not.)

<snip>
 
W

Willem

tom st denis wrote:
)> If you want to be more concise, you can make ERROR_SUCCESS equal 0,
)> and then you can simply do:
)>
)> ? ? rv = BigNum_Initialize(&a) || ... || BigNum_Set(&b, 3) || BigNum_AddTo(&a, &b) || ... ;
)>
)> Which will short-circuit at the first failure.
)
) Which in a sufficiently complicated function like an ECC point add/
) double would result in spaghetti code and your termination. :)

How can it be spaghetti when it's a single line of calls, one after
the other? Spaghetti code is meant to be tangled, isn't it?

) The "if (err == OK) ..." method is cool but frankly I just like the
) goto's when I'm writing out something the long way.

I've seen the (err == OK) method used too much in code that didn't require
cleanup at end of function. Apparently somebody at our company believes in
'one-function-one-return' religiously. I agree gotos are better.


Another method is to have all 'bignum' functions check for 'error' inputs
and generate 'error' outputs when they encounter them (or whenever they
encounter an error), and then you only have to check for an 'error'
result at the very end.

Something like:

int DoCalc()
{
int rv;
BigNum a;
BigNum b;
BigNum c;
BigNum d;

BigNum_Init(&a);
BigNum_Init(&b);
BigNum_Init(&c);
BigNum_Init(&d);

BigNum_Set(&a, 3);
BigNum_Set(&b, 5);
...
BigNum_Multiply(&c, &d);
...
if ((rv = BigNum_GetError(&d)) != ERROR_SUCCESS) return rv;

BigNum_Print("The result is: %bn", &d);
BigNum_Free(&a);
BigNum_Free(&b);
BigNum_Free(&c);
BigNum_Free(&d);

return ERROR_SUCCESS;
}

The only weirdside to this is when a value is not used in the final result,
then that value will not be checked for errors.


SaSW, Willem
--
Disclaimer: I am in no way responsible for any of the statements
made in the above text. For all I know I might be
drugged or something..
No I'm not paranoid. You all think I'm paranoid, don't you !
#EOT
 
J

Johann Klammer

mike3 wrote:
[...]
But we've violated Djikstra and we've used a "bad" goto. How would
_you_ write MyFunc() above?

Hello,

If you want to get rid of the gotos and the ifs, you might end up using
some kind of object stack. Every time you create or initialize
something, you push a handle and a destructor onto the stack. If
something goes wrong, you work your way down the stack destroying all
the resources. A bit like pthread_cleanup_push/pop. Of course it is
kludgy, may involve unsafe casts and a bit of additional overhead. I do
not use it much myself.

There is an implementation here:
http://members.aon.at/~aklamme4/dvbv/dvbvulture_1.0.23.tar.gz
In the files common/custck.c include/custck.h

(please ignore the code smell)

JK
 
M

mike3

"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.
 
M

Mark Bluemel

this is my way...

Why does this not surprise me?
ErrorCode MyFunc(void)
{BigNum a, b, c, d;
 ErrorCode  rv, rk;

 if((rv=BigNum_Initialize(&a))!= ERROR_SUCCESS)
                             return  rv;
 if((rv = BigNum_Initialize(&b)) != ERROR_SUCCESS)
    {ex1:   BigNum_Free(&a); return  rv;}
 if((rv = BigNum_Initialize(&c)) != ERROR_SUCCESS)
    {ex2:   BigNum_Free(&b); goto   ex1;}
 if((rv = BigNum_Initialize(&d)) != ERROR_SUCCESS)
    {ex3:   BigNum_Free(&c); goto   ex2;}
 if((rv = BigNum_Set(&b, 3))     != ERROR_SUCCESS)
    {ex4:   BigNum_Free(&d); goto   ex3;}

Ouch!
 
T

Tim Rentsch

mike3 said:
I've got some stuff that looks like this:

---
ErrorCode MyFunc()
{
BigNum a, b, c, d;
ErrorCode rv;

rv = BigNum_Initialize(&a);
if(rv != ERROR_SUCCESS)
return(rv);

rv = BigNum_Initialize(&b);
if(rv != ERROR_SUCCESS)
{
BigNum_Free(&a); /* and what if THIS fails??? :) */
return(rv);
}

rv = BigNum_Initialize(&c);
if(rv != ERROR_SUCCESS)
{
BigNum_Free(&b);
BigNum_Free(&a);
return(rv);
}

rv = BigNum_Initialize(&d);
if(rv != ERROR_SUCCESS)
{
BigNum_Free(&c);
BigNum_Free(&b);
BigNum_Free(&a);
return(rv);
}

/* do some math */
rv = BigNum_Set(&b, 3);
if(rv != ERROR_SUCCESS)
{
BigNum_Free(&d);
BigNum_Free(&c);
BigNum_Free(&b);
BigNum_Free(&a);
return(rv);
}

...

/* free buffers */
rv = BigNum_Free(&d);
if(rv != ERROR_SUCCESS)
{
BigNum_Free(&c);
BigNum_Free(&b);
BigNum_Free(&a);
return(rv);
}

rv = BigNum_Free(&c);
if(rv != ERROR_SUCCESS)
{
BigNum_Free(&b);
BigNum_Free(&a);
return(rv);
}

rv = BigNum_Free(&b);
if(rv != ERROR_SUCCESS)
{
BigNum_Free(&a);
return(rv);
}

rv = BigNum_Free(&a);
if(rv != ERROR_SUCCESS)
return(rv);

return(ERROR_SUCCESS);
}
---

But this is nasty, with all those duplicated "free" blocks. What can
be done to relieve this code duplication? It's ugly, it hurts my
freaking eyes and my nose is screaming for relief. And it hurts
maintainability to no end -- what if we decide to add more capability
to MyFunc() that requires additional bignums? Oy... We'd have to
update _all those blocks_.

I was thinking about using a "goto", but Gotos are Bad, aren't they? I
can't believe I may have to use a goto! What about Djikstra's famous
letter? Is it possible that perhaps a goto may be GOOD here? Because
with a goto this would all seem much simpler. If we violate Djikstra,
we get:

---

ErrorCode MyFunc()
{
BigNum a, b, c, d;
ErrorCode rv;

rv = BigNum_Initialize(&a); if(rv != ERROR_SUCCESS) goto
fail0;
rv = BigNum_Initialize(&b); if(rv != ERROR_SUCCESS) goto
fail1;
rv = BigNum_Initialize(&c); if(rv != ERROR_SUCCESS) goto
fail2;
rv = BigNum_Initialize(&d); if(rv != ERROR_SUCCESS) goto
fail3;

/* do some math */
rv = BigNum_Set(&b, 3); if(rv != ERROR_SUCCESS) goto fail4;

...

/* free buffers */
rv = BigNum_Free(&d); if(rv != ERROR_SUCCESS) goto fail3;
rv = BigNum_Free(&c); if(rv != ERROR_SUCCESS) goto fail2;
rv = BigNum_Free(&b); if(rv != ERROR_SUCCESS) goto fail1;
rv = BigNum_Free(&a); if(rv != ERROR_SUCCESS) goto fail0;

return(ERROR_SUCCESS);

fail4: BigNum_Free(&d);
fail3: BigNum_Free(&c);
fail2: BigNum_Free(&b);
fail1: BigNum_Free(&a);
fail0: return(rv);
}

Your question is really about style. As is usually the case in
such matters, there are no right or wrong answers, just individual
preference, and perhaps local culture that should also be taken
into account.

That said, when a question like this comes up, usually it's a
strong indication that the program design is wrong at some
higher level. If you find yourself writing lots of ugly
functions, there's a good chance it will be worth rethinking
some higher-level decisions and see if a different approach
leads to cleaner code at the function level.
 
M

mike3

Your question is really about style.  As is usually the case in
such matters, there are no right or wrong answers, just individual
preference, and perhaps local culture that should also be taken
into account.

That said, when a question like this comes up, usually it's a
strong indication that the program design is wrong at some
higher level.  If you find yourself writing lots of ugly
functions, there's a good chance it will be worth rethinking
some higher-level decisions and see if a different approach
leads to cleaner code at the function level.

So what would the "bad design" be in this case? There doesn't
seem to be a whole lot of things that could be changed. A bignum
system will have init, free, and arithmetic routines. Trouble
seems to be due to the possibility of return codes on every
bignum operation. Is this the "design problem"? Notice that
something like, say, GMP, doesn't do that. All GMP functions
return void or an arithmetic result, not an error code. But I *do*
want to have some sort of error reporting functionality.
 
B

BartC

mike3 said:
So what would the "bad design" be in this case? There doesn't
seem to be a whole lot of things that could be changed. A bignum
system will have init, free, and arithmetic routines. Trouble
seems to be due to the possibility of return codes on every
bignum operation. Is this the "design problem"?

Sounds like it.

But whatever you end up doing, perhaps consider the possibility of having a
second set of functions that shadow the main ones (and which could wrap the
main functions).

These secondary functions take care of error-checking. Then the programmer
can do more productive work without worrying about errors 90% of the time.

Of course it depends on what sort of errors are likely; if they are rare,
and an abort (or one of those longjmps) is acceptable when there is a
problem, then this can be workable. But it is also up to the programmer
which set of functions to call.

It depends also how the code that uses this library fits into the overall
program; if this is just servicing something bigger, such as a language
runtime, then you don't want minor numeric problems to bring down the
system.

In any case, ignoring checking an errorcode shouldn't bring down the system
either; but this could need *more* checking inside the library itself (which
you may or may not have access to).
 
M

mike3

Sounds like it.

But whatever you end up doing, perhaps consider the possibility of havinga
second set of functions that shadow the main ones (and which could wrap the
main functions).

These secondary functions take care of error-checking. Then the programmer
can do more productive work without worrying about errors 90% of the time..

Of course it depends on what sort of errors are likely; if they are rare,
and an abort (or one of those longjmps) is acceptable when there is a
problem, then this can be workable. But it is also up to the programmer
which set of functions to call.

It depends also how the code that uses this library fits into the overall
program; if this is just servicing something bigger, such as a language
runtime, then you don't want minor numeric problems to bring down the
system.

In any case, ignoring checking an errorcode shouldn't bring down the system
either; but this could need *more* checking inside the library itself (which
you may or may not have access to).

However, some of these errors can't simply be ignored, like an "out of
memory"
error or "out of disk space" error. And it would also be a nice
feature to know
if something has gone wrong with the computation.
 
M

mike3

<snip>

I've made an interesting and disturbing discovery.

I found THIS:

http://www.isc.org/software/bind/991

I decided to have a look at the source code, and oh my goodness! What
did I find?!
Oh NO....!!!!!!!!!!!!!!!!!!!!!!! :

----

idn_result_t
idn_normalizer_add(idn_normalizer_t ctx, const char *scheme_name) {
idn_result_t r;
void *v;
normalize_scheme_t *scheme;

assert(ctx != NULL && scheme_name != NULL);

TRACE(("idn_normalizer_add(scheme_name=%s)\n", scheme_name));

assert(INITIALIZED);

if (idn__strhash_get(scheme_hash, scheme_name, &v) != idn_success) {
ERROR(("idn_normalizer_add(): invalid scheme \"%-.30s\"\n",
scheme_name));
r = idn_invalid_name;
goto ret;
<----------------- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
}

scheme = v;

assert(ctx->nschemes <= ctx->scheme_size);

if (ctx->nschemes == ctx->scheme_size &&
(r = expand_schemes(ctx)) != idn_success) {
goto ret;
<----------------- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
}

ctx->schemes[ctx->nschemes++] = scheme;
r = idn_success;
ret:
<----------------- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
TRACE(("idn_normalizer_add(): %s\n", idn_result_tostring(r)));
return (r);
}

----

YES, that's right -- those are GOTOs in there, and they do this a LOT
in this program! Djikstra!!!

But as this thread seems to show, it looks like that there exist
alternatives
to GOTO, and so we can still uphold Djikstra's law. Or is there
something
about this program that makes it OK to use GOTOs in it, but it is not
OK
to use them in the one I'm doing?
 
M

mike3

<snip>

I've also noticed another problem. I've got a place where a
routine needs to _return_ a BigNum it mints inside itself.

ErrorCode MyFunc(BigNum **OutputValue)
{
...
*OutputValue = (BigNum *)malloc(sizeof(BigNum));
if(*OutputValue == NULL)
{
<duplicated err handler>
}

rv = BigNum_Initialize(*OutputValue);
if(rv != ERROR_SUCCESS)
{
<ditto>
}

...
(error handlers in here that now have to _free the alloced
memory
and not just deinit the BigNum_! What should I do about
this?)
...
}

What to do?
 
T

Tim Rentsch

mike3 said:
[snip]

Your question is really about style. As is usually the case in
such matters, there are no right or wrong answers, just individual
preference, and perhaps local culture that should also be taken
into account.

That said, when a question like this comes up, usually it's a
strong indication that the program design is wrong at some
higher level. If you find yourself writing lots of ugly
functions, there's a good chance it will be worth rethinking
some higher-level decisions and see if a different approach
leads to cleaner code at the function level.

So what would the "bad design" be in this case?

To answer that question one would need both more specific information
and more context. The example you gave was fairly limited and also
rather abstract. Not saying there is anything wrong with that, there
isn't, but it doesn't provide enough information to answer the
followup question.
There doesn't seem to be a whole lot of things that could be
changed. [snip elaboration]

Undoubtedly there are lots of things that can't be changed, but
unless the situation is very different from what I would expect there
still are lots of design choices available. Maybe you have locked in
on some unconscious assumptions? I'm sorry I don't have a better
answer for you, but the question you're asking now is much larger
than the original topic.
 
B

Ben Bacarisse

mike3 said:
<snip>

I've made an interesting and disturbing discovery.

I found THIS:

http://www.isc.org/software/bind/991

I decided to have a look at the source code, and oh my goodness! What
did I find?!
Oh NO....!!!!!!!!!!!!!!!!!!!!!!! :

----

idn_result_t
idn_normalizer_add(idn_normalizer_t ctx, const char *scheme_name) {
idn_result_t r;
void *v;
normalize_scheme_t *scheme;

assert(ctx != NULL && scheme_name != NULL);

TRACE(("idn_normalizer_add(scheme_name=%s)\n", scheme_name));

assert(INITIALIZED);

if (idn__strhash_get(scheme_hash, scheme_name, &v) != idn_success) {
ERROR(("idn_normalizer_add(): invalid scheme \"%-.30s\"\n",
scheme_name));
r = idn_invalid_name;
goto ret;
<----------------- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
}

scheme = v;

assert(ctx->nschemes <= ctx->scheme_size);

if (ctx->nschemes == ctx->scheme_size &&
(r = expand_schemes(ctx)) != idn_success) {
goto ret;
<----------------- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
}

ctx->schemes[ctx->nschemes++] = scheme;
r = idn_success;
ret:
<----------------- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
TRACE(("idn_normalizer_add(): %s\n", idn_result_tostring(r)));
return (r);
}

It's Dijkstra, OK? Dijkstra.

The above is a terrible example. It looks like programming by numbers:
"I've been told to trace entry and exit so I'll stick a label at the end
and replace returns with gotos". I think it's clearer without them,
although there is some justification if my guess is right -- it might
have been a ploy to minimise re-writing.
But as this thread seems to show, it looks like that there exist
alternatives to GOTO, and so we can still uphold Djikstra's law.

It's not a law. The famous article is a well-argued case. Do read it
if you haven't.
Or is there something about this program that makes it OK to use GOTOs
in it, but it is not OK to use them in the one I'm doing?

I don't know the program, but this function does not benefit from having
gotos. There are cases where a goto really helps, but they are far
fewer than some people think.
 
I

Ian Collins

<snip>

I've also noticed another problem. I've got a place where a
routine needs to _return_ a BigNum it mints inside itself.

ErrorCode MyFunc(BigNum **OutputValue)
{
...
*OutputValue = (BigNum *)malloc(sizeof(BigNum));
if(*OutputValue == NULL)
{
<duplicated err handler>
}

rv = BigNum_Initialize(*OutputValue);
if(rv != ERROR_SUCCESS)
{
<ditto>
}

...
(error handlers in here that now have to _free the alloced
memory
and not just deinit the BigNum_! What should I do about
this?)
...
}

What to do?

Use C++!

You appear to be using C++ already given your remarkably similar flame
bait thread over on c.l.c++.
 
W

Willem

mike3 wrote:
) So what would the "bad design" be in this case? There doesn't
) seem to be a whole lot of things that could be changed. A bignum
) system will have init, free, and arithmetic routines. Trouble
) seems to be due to the possibility of return codes on every
) bignum operation. Is this the "design problem"? Notice that
) something like, say, GMP, doesn't do that. All GMP functions
) return void or an arithmetic result, not an error code. But I *do*
) want to have some sort of error reporting functionality.

A better design would be to catch errors into the bignum type itself,
and propagate that through the calculations. I.E.: Have 'error' be
a possible value for a bignum (or perhaps multiple 'error' values)
and whenever an 'error' value is used in a calculation, the result
is also (that same) 'error'.

That way, you can calculate away without error handling and at the
end do one single error check to see if everything went well.


SaSW, Willem
--
Disclaimer: I am in no way responsible for any of the statements
made in the above text. For all I know I might be
drugged or something..
No I'm not paranoid. You all think I'm paranoid, don't you !
#EOT
 
M

mike3

It's Dijkstra, OK? Dijkstra.

OK...

The above is a terrible example. It looks like programming by numbers:
"I've been told to trace entry and exit so I'll stick a label at the end
and replace returns with gotos". I think it's clearer without them,
although there is some justification if my guess is right -- it might
have been a ploy to minimise re-writing.

To me, it looks like they're trying to reuse the TRACE call. So that
one
could modify the TRACE call without having to modify a clone at
another
point in the routine. Would it be OK to duplicate the line at each
return
point?
It's not a law. The famous article is a well-argued case. Do read it
if you haven't.


I don't know the program, but this function does not benefit from having
gotos. There are cases where a goto really helps, but they are far
fewer than some people think.

Mmmm. So how would you write those returns?
 
T

tom st denis

tom st denis wrote:

)> If you want to be more concise, you can make ERROR_SUCCESS equal 0,
)> and then you can simply do:
)>
)> ? ? rv = BigNum_Initialize(&a) || ... || BigNum_Set(&b, 3) || BigNum_AddTo(&a, &b) || ... ;
)>
)> Which will short-circuit at the first failure.
)
) Which in a sufficiently complicated function like an ECC point add/
) double would result in spaghetti code and your termination.  :)

How can it be spaghetti when it's a single line of calls, one after
the other?  Spaghetti code is meant to be tangled, isn't it?

) The "if (err == OK) ..." method is cool but frankly I just like the
) goto's when I'm writing out something the long way.

I've seen the (err == OK) method used too much in code that didn't require
cleanup at end of function.  Apparently somebody at our company believes in
'one-function-one-return' religiously.  I agree gotos are better.

Another method is to have all 'bignum' functions check for 'error' inputs
and generate 'error' outputs when they encounter them (or whenever they
 encounter an error), and then you only have to check for an 'error'
result at the very end.

Something like:

int DoCalc()
{
  int rv;
  BigNum a;
  BigNum b;
  BigNum c;
  BigNum d;

  BigNum_Init(&a);
  BigNum_Init(&b);
  BigNum_Init(&c);
  BigNum_Init(&d);

  BigNum_Set(&a, 3);
  BigNum_Set(&b, 5);
  ...
  BigNum_Multiply(&c, &d);
  ...
  if ((rv = BigNum_GetError(&d)) != ERROR_SUCCESS) return rv;

  BigNum_Print("The result is: %bn", &d);
  BigNum_Free(&a);
  BigNum_Free(&b);
  BigNum_Free(&c);
  BigNum_Free(&d);

  return ERROR_SUCCESS;

}

The only weirdside to this is when a value is not used in the final result,
then that value will not be checked for errors.

That only works if you capture the error codes in your bignum
structure otherwise it's not thread safe.

Tom
 

Ask a Question

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.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,800
Messages
2,569,657
Members
45,408
Latest member
SandyR2016
Top