Hacker News new | ask | show | jobs
by davidsiems 5575 days ago
You can accomplish this by using asserts in your code.

You don't need exceptions to get a callstack and you can assert that values are valid and force a crash / callstack dump when they're not.

On top of that, you can compile the asserts out for release builds if you're confident they won't be hit.

2 comments

Normally you turn assert() off for production buids, so it's not a robust solution, as some error conditions may not get generated in testing.

The memory protection catches a lot of out-of-bounds memory references pretty well, and if you enable core dumps, you can extract neat backtrace from the core file (provided your routines fail-forward in case of invalid arguments). Moreover, some compilers can be instructed to instrument your code & data, including stack, with guard data, meant to trip process if it accesses wrong memory region. GNU malloc does some guardians if you sest $MALLOC_CHECK_.

If you aren't worried about vendor lock-in, you can use GCC's __attribute__((warn_unused_result)) [1]

--

[1] http://sourcefrog.net/weblog/software/languages/C/warn-unuse...

And if you are worried about vendor lock-in, you can hide it behind a #define
Sure I can use asserts. But I'm as likely to forget the assert() as I am to forget to check the return value.

And even if I did: If you consider the faulty main() in the linked article: How would you use assert() there to make sure that result as used after the call to foo() is actually usable? If foo() returns -1 (because any of the calls to divide returned -1) then result is undefined.

You would put an assert inside of the divide function like so:

  int divide (int x, int y)
  {
    defend (y != 0);
    return x / y;
  }
Now divide is guaranteed to produce a correct result if it's called with correct data. It's up to the caller to make sure the data is correct, or an assert will happen.

Just to finish out the example to show how much cleaner asserting is compared to error handling:

  int foo(void)
  {
    volatile int x = 4, y = 28;
    return divide(x, y) + divide(y, x);
  }

  int main ()
  {
    return foo();
  }
That's not to say that error handling doesn't have its place, but it should only be used for data that you can't anticipate.
That's a precondition check. While useful (essential), it doesn't cover all "forgot to check an error code" cases.
If you're really trying to write efficient code (which is what this article claims to care about) you don't 'forget' to do things like assert that the data is correct. You _guarantee_ that the data is correct and then you process it as fast as you can without having to check.

You only run into trouble with this method if the data verification step is the computationally expensive operation.