13
\$\begingroup\$

It's good practice to use assert to test that things are that we expect them to be. Likewise, it's good to check that calls worked as expected and didn't error.

In a (simple) embedded system, if either of these fail, we probably just want to reset. However, under development, we don't want to reset, but want to catch the error.

What's best practice to handle this? I currently use asm("bkpt") if assertions fails, which works well for development. What about in actual use - I'd like it to reset if no debugger is attached.

\$\endgroup\$
1

9 Answers 9

15
\$\begingroup\$

The assert function as such is problematic, as you've probably noticed since you asked this question. No, using it is not good practice. Do not mix up "lots of error checking" with assert.

Like many things in C, assert originates from Unix and it kind of assumes that "notify the OS, then lay down and die" is acceptable program behavior. You'd just terminate the executable and the OS would display some error message, end of story. Not really feasible in an embedded system though, is it...

Furthermore, it has always been a pain to make assert spit out meaningful information about the cause. And per definition, assert is only present in the debug build of the project while NDEBUG is defined. So it was never meant to be used for anything else but quick & dirty debugging in the development phase.

(In fact assert is even problematic in its intended environment, an old problem which has not been addressed until every recently - advanced topic for those interested: N2829 Make assert() macro user friendly for C and C++. These changes are live in C23/C++23.)

The conclusion is that assert should be avoided in all (embedded) systems.


So what to use instead?

First it is worth mentioning that the C11 version of C provided a different, much better debug tool namely _Static_assert, which does compile-time checking instead of run-time checking. This is a very nice one as long as the error is something you can check for at compile-time. Array sizes, read-only data integrity, stuff like that. But it can't be used to check things like run-time values of variables. So it isn't really a replacement for assert, but rather a complement.

(As from C23 we can use static_assert, lower case. It's been made a proper keyword now.)


You also mention "we probably just want to reset". Indeed, this is the most sensible thing to do in case of critical errors in a standard MCU application. Whenever errors happen, we generally want to reset the MCU in case the problems are related to register setups, memory corruption or similar.

However, in safety-related and real-time applications, it might not be advisable to just reset either, unless a seamless reset can happen.


The best solution to all of these problems in my experience is to always implement a proper error handler. Use static_assert for program integrity stuff at compile-time, but beyond that do not separate "debugging errors" from actual errors in the live release version. Similarly, errors that pop up as a result of defensive programming (ie "this should never happen but what if it does anyway") should also get passed on to this error handler.

A rugged, sensible design of a high-integrity, "bare metal" MCU application will therefore most often look like this:

void main (void)
{
  /* various init code here */

  for(;;)
  {
    kick_wdog();

    result = state[current_state]();
    current_state = error_handler(result, current_state);
  }
}
  • kick_wdog is ideally the only place in the program where you kick the watch dog. Sometimes "time window" solutions can be used here and one might clock the time it takes to execute a state etc, but this is the fundamental idea.

  • state is a state machine, most often implemented as an array of function pointers allocated in flash. This can be as simple or as advanced as needed, but on the top level this should probably just be "system is starting up", "system is in a safe mode" and "system is fully operational".

    High-integrity systems always implement a safe mode. If this involves some "limp home" code or just error logging/reporting followed by a hard reset depends on the application.

  • result is either OK or an error code. Notably all errors that may occur in your application may "trinkle down" to this.

    It can go like in this example: error happens in a SPI driver. SPI driver reports up to the SPI protocol module. The SPI protocol module may or may not report the error further up to the caller, depending on the error. Lets say that the caller was some LCD driver - which in turn might report an error upwards. At each layer of the program you have a decision whether the error is relevant enough to pass on or not, until they all eventually end up in the top level main().

  • error_handler is the only place in the program where the decision what to do in case of an error occurs. It may do the error reporting/logging, it may decide to revert to a safe mode, it may decide to reboot the MCU etc etc. Similarly, this centralized location is the only place in the program where state changes may occur.

    Having de-centralized error decision handling code all over the program is bad - it makes it hard to track down where the error came from. Similarly, having de-centralized state change decisions (aka "stateghetti programming") is also very hard to maintain or even get an overview of. Instead of making the decision, the various modules just report a result code. The error handler then makes the decision based on the result code and the current state.

The above also have various other benefits such as "no debug code". It has low "cyclomatic complexity" (execution paths) and it makes it easy to test code coverage (all code present in the program should get executed at some point). This in turn makes it easier to deal with safety standards, if applicable.

And yeah lot of the above are design patterns coming from my usual stomping grounds of safety-critical applications. But a whole lot of what safety-related programming actually entails is just: don't write bugs, don't write unmaintainable programs, don't write programs where the programmer themselves have no clue what's going on, etc. Essentially just "don't write bad programs" and general software quality concerns.

\$\endgroup\$
8
  • 1
    \$\begingroup\$ Great post! I'm accepting this answer because I learned so much from it. Even if I don't adopt all you say, it got the wheels turning well. \$\endgroup\$ Sep 8 at 17:28
  • 1
    \$\begingroup\$ @SRobertJames I think these kind of discussions between engineers are valuable to have more often indeed. Many think that all these things mentioned are just subjective stuff you can come up with as you go, but there's actually plenty of field use experience and research about what's best practices and what isn't, if you know where to look for it. A good place to start is studying MISRA C, even if you don't happen to design mission-critical software. I don't think it makes any particular recommendation regarding assert, but plenty regarding "debug builds", "commented-out code" etc. \$\endgroup\$
    – Lundin
    Sep 8 at 20:17
  • 1
    \$\begingroup\$ @Artfaith current_state in this pseudo-code:ish example is the state currently getting executed by the state machine, which is also used as the index to the array of function pointers which acts as the state machine (essentially that's just another flavour of switch but faster and safer). It would be a variable local to main(), likely staticm just as the function pointer array. Here is a more concrete example: electronics.stackexchange.com/a/96415/6102 \$\endgroup\$
    – Lundin
    Sep 8 at 20:21
  • 1
    \$\begingroup\$ @Artfaith Regarding Unix and C, that's how C was developed. The inventor of C, Dennis Ritchie worked at Bell Labs/AT&T and was tasked with developing the programming language to be used for the Unix OS. The result was C. So once upon a time C was pretty much tailored to suit a hosted system Unix environment, as was what later became the C standard library. C eventually became much more widely used than anticipated however. It didn't really become the mainstream language for embedded ("freestanding") systems until the late 1990s. Before that, pure assembler was the King of the hill. \$\endgroup\$
    – Lundin
    Sep 8 at 20:24
  • 1
    \$\begingroup\$ @Kubahasn'tforgottenMonica It's not designed in a way that makes sense in embedded systems. To quote the standard (C11 7.2.1.1) "the assert macro writes information... /--/ ... on the standard error stream in an implementation-defined format. It then calls the abort function." (C11 7.22.4.1) "The abort function causes abnormal program termination to occur, unless the signal SIGABRT is being caught and the signal handler does not return." Embedded systems do not support (abnormal) program terminations nor do they usually implement signal handling. \$\endgroup\$
    – Lundin
    Sep 8 at 20:31
17
\$\begingroup\$

It depends.

On an ARM platform there are very low level error handlers which indicate that some kind of low level problem has occurred (e.g. illegal memory access) and these need to have specific handlers implemented. These are not ASSERTS but you should first ensure that they are implemented and also that they are functional (they might for instance give some kind of serial output before resetting, or attempt to write error registers and stack frame to FLASH.

I am a believer that ASSERTS should be all over your code and that they SHOULD be left in, and that the ASSERT handler should return the system to a known state. In development, this might be an infinite loop, writing some diagnostic info (at least file and line number) to serial out, and flashing a red LED.

In production, a watchdog can be added which means that the system will reboot. In this case, there should again be an attempt to save an error record to non volatile memory, and the boot sequence should look for such records and do something appropriate to indicate that a problem has occurred.

Taking ASSERTS out in production code seems to me to be the worst of all possible worlds. An ASSERT failure is an indication that the system has entered unknown territory - a function has been passed an illegal arg perhaps, in any case, the "impossible" has happened. We are no longer able to guarantee correct operation of the system, and we need to take serious action to get back to the land of sanity. To "just carry on" because we are now in the field is not a responsible policy.

Note that this implies that ASSERT should really only be used for events that should not, ever, occur. For errors that indicate that there might be a problem, but not that the system is really failing in some unexpected way - some sort of event logging system is needed.

\$\endgroup\$
14
  • 4
    \$\begingroup\$ In development, this might be an infinite loop, writing some diagnostic info (at least file and line number) to serial out, and flashing a red LED. +1, is exactly what I did but I used the LCD of my project instead of serial. Managed to find a lot of bugs that way. \$\endgroup\$
    – Marco
    Sep 7 at 14:06
  • 4
    \$\begingroup\$ @Marco Unfortunately, most of the embedded systems have no way to communicate such messages to a user. This solution might work only for that minor number of systems. \$\endgroup\$ Sep 7 at 15:39
  • 3
    \$\begingroup\$ The main difference between an assert and an if is that assertions are documentation. As such, you can sprinkle them all over the code, with heavy conditions, and all of them will be compiled away on production releases. If you need to test a condition at runtime, use if. If you need to document a prerequisite use assert. If hazard_func(int x) is so dangerous that its parameter must be in [0,5] then, please, use an if. It's part of the semantics of this very function to double-check its input. Assertions have zero context to make a decision and are not a safety mechanism. \$\endgroup\$ Sep 7 at 16:18
  • 1
    \$\begingroup\$ Great answer. "In development, this might be... In production, a watchdog can be added...". How would you recommend telling the system if it's in development or production? Would you have two builds with two different image? The problem here is then that we're developing on a different image, which can introduce other, unanticipated changes (due to errors preparing two distinct images). My ideal would be for the device to behave differently if there's a debugger attached - that way, I can use the same exact image for both. \$\endgroup\$ Sep 7 at 23:57
  • 2
    \$\begingroup\$ @SRobertJames Debug/release build is often either handled by picking one or the other in some IDE and/or toggling some flag. I strongly recommend implementing some manner of detection that the debug build is being used (and therefore the wdog is disabled). Some red LED lighting up or whatever is feasible. Debug builds accidentally making their way to production is otherwise incredibly common and very dangerous. \$\endgroup\$
    – Lundin
    Sep 8 at 14:42
5
\$\begingroup\$

There are more or less two kinds of unexpected situations in a (simple) embedded system: software bugs and hardware failures.

Software bugs should be all eliminated during development and debugging. Hardware failures (and wrong user actions) must be detected and corrected by the software. A lack in monitoring these hardware failures should be considered a software bug!

I think that resetting the system in case of abnormal situation is the worst of the possible solutions, because:

a) Maybe that a reset is not as safe as, for example, stopping entirely the apparatus.

b) Maybe after a reset the apparatus re-enters in the same anomaly, creating a loop which can also be dangerous or, at least, undesirable.

c) If there are several reasons the apparatus can reset, then it is impossible to understand which one is the culprit. The users will not be able to give information about the problem they are facing.

d) The reset can be (and typically is) performed by the user, when he feels something is wrong.

If the system has some way to indicate an error code, then it is best to stop everything (I mean stop in a safe condition), and show an error code.

Asserts are used extensively in software for non-embedded systems, especially in libraries, to prevent misuse and, sometimes, for "hardware errors" like not having enough memory, or encountering I/O failures. But libraries are not applications, they do not have knowledge of the complete system. In an embedded system the firmware should have complete knowledge and control. If a call to a subroutine/function failes, the application should be notified in order to manage the problem. Or, in a simpler way, the function can lock the system or, maybe, refuse to proceed. But this behavior must be evaluated and reasoned about.

I can add that there can be situations where a simple reset is a good idea, but I don't see many of them.

\$\endgroup\$
10
  • 3
    \$\begingroup\$ the weasel word here is "should". In reality there is no way to say that this has occurred with absolute certainty. Therefore it is wise and necessary to design systems in such a way that things have the best chance of failing safely when the crystal ball turned out to be faulty. It is impossible to generalise (it depends on the specific system) but this would be the best possible scenario. \$\endgroup\$
    – danmcb
    Sep 7 at 12:40
  • 1
    \$\begingroup\$ @TimWilliams perhaps I should have written "in most real world systems there is no way to say ...". Even where such a proof is believed to exist, my take is that a paranoid view of the world is a good idea, when this level of reliability really matters. (Of course we are all generalising here.) \$\endgroup\$
    – danmcb
    Sep 7 at 13:36
  • 3
    \$\begingroup\$ Unfortunately there is a third class of unexpected situation - software errors caused by hardware issues (think: bug in CPU instruction; cosmic ray flips bit in memory; electrical noise causes glitch on bus when transferring data etc). In these cases the problem often manifests like a software bug (program reaches a supposedly "impossible" state), and the underlying hardware issue is typically not something that can be detected/corrected. These are the cases where it makes sense to use some sort of "assert", though what it should actually do will of course depend hugely on the situation. \$\endgroup\$
    – psmears
    Sep 7 at 15:40
  • 1
    \$\begingroup\$ @psmears, aren't cosmic rays or glitches in the bus hardware failures? A reasonable hardware tries to resist those stimuli but sometimes it can fail anyway. For example many MCUs have a BOR (brownout) protection that is programmable: you can choose to ignore, to reset, or to implement a handler for them. The reset option is there because it is difficult to think that an underpowered MCU can work reliably but the possibility to write a handler is there because sometimes something better than a simple reset can be done. \$\endgroup\$ Sep 8 at 5:13
  • 1
    \$\begingroup\$ @Unknown123 The point of these is to catch events where the state of the micro has been forcibly changed somehow and now is indeterminate. By definition then, being forced into the assert state cannot be a false alarm - it's merely another (vanishingly rare) way to hit the assert. BTW, I once worked on safety-related software where we did actually have to detect this, and there were some interesting defence-in-depth features there. \$\endgroup\$
    – Graham
    Sep 8 at 8:08
3
\$\begingroup\$

You can re-target assert() to your own function.
There should be a weak __aeabi_assert() for you to override.

Assert also doesn't do anything when compiled with release with NDEBUG.

If you want to breakpoint in your failure handler, asm("bkpt") will work only with a debugger present, CoreDebug->DHCSR & CoreDebug_DHCSR_C_DEBUGEN_Msk should catch this.

\$\endgroup\$
3
  • \$\begingroup\$ Thanks. What do you mean "asm("bkpt") will *work only..." - what does "work" mean? What happens if I execute asm("bkpt") without a debugger present? \$\endgroup\$ Sep 7 at 23:47
  • \$\begingroup\$ @SRobertJames On most MCUs your software will either stall or reset on a breakpoint with no debugger. \$\endgroup\$ Sep 8 at 11:47
  • \$\begingroup\$ On the CortexM0 for example bkpt without the debugger attached triggers the hard fault exception, which can be handled. \$\endgroup\$
    – Rodney
    Sep 9 at 8:14
3
\$\begingroup\$

If you can foresee a potential error by inserting a respective assert, you should instead implement a serious error handling.

Each source code location and each error requires its individual consideration concerning severity, necessary resource handling, how to continue, and so on. Commonly you can not use a generic error handler.

There is no silver bullet in professional software development. And assert() is not even near.

\$\endgroup\$
7
  • \$\begingroup\$ "There is no silver bullet" does not imply "there are no useful defaults". \$\endgroup\$ Sep 7 at 23:51
  • 1
    \$\begingroup\$ @SRobertJames Except it does at the platform level. There are useful default patterns for the engineer to follow at the application level, and that's fine, but we can't make assumptions at the platform level about what's safe for the application level. As an example, I once worked on safety-related software for a hybrid electric engine. A software fault in the management controller had to not only pull power for the engine, but also pull power for the electric motors which were run by a separate controller. Simply resetting the management controller was not good enough. \$\endgroup\$
    – Graham
    Sep 8 at 8:12
  • \$\begingroup\$ @Graham Really? If I was designing such a system, I'd build those functions into the reset handler, self-contained. The alternative, of trying to do them after a fault, is risky: since things are corrupted (memory, tasks, pointers, etc.), you don't know what your code will do. Will it really pull engine power? Will it really cut the electric motors? Under a fault, you can't reason about any of these. Whereas, if your reset code does it clean, starting from an explicitly undefined state (ie we know nothing but what the reset code does), you're better off. \$\endgroup\$ Sep 8 at 14:00
  • \$\begingroup\$ @Graham Additionally: What about hardware faults? What about power loss? Your reset handler must be able to pull engine power under these circumstances. The reset handler should 1. Immediately establish a safe system (i.e. pull engine power, pull electric motor power). 2. Establish and verify a safe and controlled system 3. Only then reenegage the motors. \$\endgroup\$ Sep 8 at 14:02
  • \$\begingroup\$ There are design patterns that come pretty close to a "silver bullet" and it does involve a centralized error handler. Centralized does not necessarily mean "dumb" however, it needs to directly or indirectly contain detail knowledge of the application. I posted an answer with an example of this. \$\endgroup\$
    – Lundin
    Sep 8 at 14:44
1
\$\begingroup\$

I'd like it to reset if no debugger is attached

Seems easy enough, just replace assert() with reset() in your release build, perhaps via macro substitution.

What's best practice to handle this?

The best practice depends on how you define "safe" and "unsafe" states of your system. For instance, a car will stop if the throttle pedal breaks. An airplane will instead stay above stall speed. An electronic front door lock will remain closed on a power outage, while a lock on an air dryer will disengage. Etc. etc.

An explicit reset (assuming your system is safe to reset) is indisputably better than simply removing the assert line and letting the software crash eventually. That "eventually" may take a long time and have all sorts of unpredictable side effects.

\$\endgroup\$
2
  • \$\begingroup\$ Thanks. What I mean is: I'd like the same build to breakpoint if debugger attached, reset if not attached. That is: Respond dynamically whether or not a debugger is attached. \$\endgroup\$ Sep 8 at 13:57
  • \$\begingroup\$ @SRobertJames Are you debugging release builds? That's not very convenient, you can't see values of all those variables that have been optimized out, breakpoints won't work as expected in some loops/branches, etc. Anyway, what happens when your software runs into a failed assert without a debugger? It could be that it already resets, just like you wanted. \$\endgroup\$ Sep 8 at 14:29
1
\$\begingroup\$

It's important to distinguish between "exceptional conditions" and "bugs". Exceptional conditions can legitimately occur under special circumstances and need to be handled, of course. Bugs on the other hand are caused by programming errors and should never ever occur.

The distinction between a bug and an exceptional condition may depend on the context: A division by zero may be an exceptional condition if the divisor can be legitimately zero (for example a measured electrical current), or a bug if the divisor cannot be legitimately zero (for example an integer representing the month of a year).

Typically, you can't reasonably "handle" a bug. Bugs need to be detected ASAP and then fixed. That's what assert() is for: Detecting bugs. assert() is not for handling exceptional conditions.

Treating bugs like exceptional conditions (i. e. handling them) usually only camouflages the problem and probably makes it worse on the long term.

Calling a serial_transmit() before a serial_init() is probably (not necessarily) a bug. You can camouflage it by putting a redundant call to serial_init() at the beginning of serial_transmit(). But an assert(is_initialized) at the beginning of serial_transmit() will make it obvious that there is probably a flaw in your system initialization sequence instead of hiding it. Again: If that's actually a bug depends on the context, so just understand this as an example.

Or passing a NULL pointer to a function may be a bug or an exceptional condition. So the course of action is either a assert(ptr != NULL) or a if (ptr == NULL) {...}.

The bottom line is that assert() intentionally escalates a bug to make a problem visible immediately instead of hiding it under the rug.

However, you rarely use the standard library assert() function in embedded systems. I usually define a macro ASSERT() that calls a custom assert handler, if the assert condition is "false". It also has some additional features to prevent unit tests and Lint from going crazy. And I don't let the NDEBUG macro disable my ASSERT(). For that purpose I use a NASSERT macro. The intention is to keep ASSERT() in the production code (some tool chains define NDEBUG by default, when performing a production build).

The custom assert handler prints debug information (usually the file name and the line number the ASSERT() failed in) to an application-specific output. The output may be the serial window of the IDE (for example using the ARM Cortex-M ITM system), a serial debug port on the actual hardware or a non-volatile memory, for example. Eventually a reset is performed to put the system back into a defined state.

Optionally some clean-up code may be executed before the reset, for example de-energizing an electrical motor. But most systems should be in a safe and defined state after a reset anyway.

I can only encourage you to use assert() as much as possible. I've lost count of how many hours, days and weeks I saved, because a properly placed assert() instantly pointed out to me the root cause and exact location of a problem. The habit of thinking about the preconditions of sections of your code alone will make you detect bugs in your mind before they are executed even once.

\$\endgroup\$
1
\$\begingroup\$

Addressing how to do an assertion fail, and not whether or when to use assertions:

Firstly regarding the bkpt instruction. You should check the manual to see what it will do without the debugger attached. For example the CortexM0 programmer's reference manual states:

The processor might also produce a HardFault or go in to Lockup if a debugger is not attached when a BKPT instruction is executed.

This is clarified in the section on fault handling:

Faults are a subset of exceptions [..] All faults result in the HardFault exception being taken or cause Lockup if they occur in the NMI or HardFault handler. The faults are:

[snip]

  • Execution of a BKPT instruction without a debugger attached.

Personally I made my own ASSERT macro and function which

  1. Logs to the serial port
  2. Stores some info in SRAM (see below)
  3. Reboot
#define ASSERT(cond) if (!(cond)) do {assert_fail(__FILE__, __LINE__);} while (0)

void assert_fail(const char * filename, int line)
{
    INTLOCK_STORE;

    INT_LOCK();
    debug_printf("BUG: %s %d %s\n", filename, line, current_thread_name());

    shared_sram_data & s = getSharedData();

    s.assertion_filename = filename;
    s.assertion_line = line;

    REBOOT(REBOOT_CODE_ASSERTION);

    // Required to prevent unused variable warning
    INT_UNLOCK();
}

shared_sram_data is a persistent struct at a known location in SRAM which survives reboots and is known by the bootloader (it does not however, survive standby mode which powers the SRAM down).

After reboot, if the reboot code is set to REBOOT_CODE_ASSERTION the following items from the shared_sram_data are logged to the journal:

  • Assertion line (__LINE__)
  • Assertion filename (__FILE__)
  • Current thread id

Note that for logging the filename, I just store the bottom two bytes of the pointer to the literal for the filename. This is because those literals are stored in the .rodata section which is in flash, and being only 64K I can index anywhere into it with 16 bits. The journal entries are invalidated when a new build is uploaded since the string literals may no longer reside at the same addresses.

\$\endgroup\$
2
  • \$\begingroup\$ Invaluable! Why do you use a do ... while(0) structure? Also: Would you consider posting the full code (perhaps on github or gist)? \$\endgroup\$ Sep 10 at 2:12
  • \$\begingroup\$ @SRobertJames see stackoverflow.com/q/154136/2216148 \$\endgroup\$
    – Rodney
    Sep 10 at 7:52
0
\$\begingroup\$

It's good practice to use assert to test that things are that we expect them to be.

Asserts have nothing to do with testing. Testing is optional and not essential for what assertions are for. Using assertions in actual test case code is a poor-man's workaround to not having decent test environment.

First of all, assertions document to the human reader what conditions should hold at a given place in program's execution. If something can be clearly written as an assertion and as a comment, the comment is not the way to go!

Second of all, assertions are true statements to the compiler that the compiler may otherwise not be able to prove itself. Optimization levels play a role here, as do the compiler implementations, of course. But, given the semantics of the language (C++ and C both AFAIK), assertion failures are undefined behavior, so all compilers are free to take us on our word and assume that what's asserted is true.

Third of all, assertions can be a way of changing control flow should an assertion turn out to be not upheld. This is optional and depends on what you want to do. And only here does the question of "what does a failed assertion do" come into play.

For systems that are well segmented (Erlang model of small processes), a failed assertion will restart the process, but if a debugger is available it will drop that process into the debugger. The debug information can be dumped out-of-band and won't increase the size of the executable, so debugging release builds is not as weird as it sounds.

For a monlithic software system, a failed assertion should trigger whatever would pass for global error recovery - a reboot, for example, or if triggered multiple times over a short wall time period, a reboot with the given subsystem's configuration reverted to "safe defaults" if such exist. The latter approach works for assertions that we reasonably suspect could be triggered by a particular non-default system configuration, and is not a cure-all. It only applies in specific situations.


What's a good way to assert in embedded microcontroller code?

  1. Write assertions instead of comments if you can. They are excellent documentation.

  2. Let the compiler use assertions to know more about the code's properties than it itself can deduce.

  3. Decide how failed assertions are handled based on the particular application's requirements. There's no global "best approach" here. But, in all cases, this concern should come last.

Use assertions whether they are "handled" in any way or not. The "handling" is incidental to what they are for.


Assertions, used as above, are an excellent tool for catching your own mistakes - even in applications we don't normally think of as "coding". I use Mathematica quite a bit in engineering design work, and assertions are there to replace comments such as "all rows of this matrix should add up to zero". The matrix is a fixed input - a part of the notebook - so an assertion seems to make little sense. But we humans make typos. So if there's an invariant, make sure to assert it. In this particular case, it did catch two typos, and I've caught hundreds of potential problems in otherwise static input by asserting what it should be even though that input doesn't change at all. This goes much farther once you assert the invariants, preconditions, and postconditions that depend on dynamic state of the computation.

\$\endgroup\$
3
  • \$\begingroup\$ Nothing about assert or abort is undefined behavior. The C standard is explicit about what will happen when assert fails. It is very much implementation-defined behavior however. And they are explicitly disabled in release build - also guaranteed by the C standard. We shouldn't confuse assert for "do extensive error checking", these are separate things. assert is a bad means to report errors. \$\endgroup\$
    – Lundin
    Sep 8 at 20:40
  • \$\begingroup\$ @Lundin: I don't think when this answer uses "Assertion" that it means "a call to assert()". (The answer would be improved by clarifying this point) \$\endgroup\$
    – Ben Voigt
    Sep 8 at 20:52
  • \$\begingroup\$ @BenVoigt The question uses assert in code formatting, which is a strong indication that the C function/macro is what the OP is concerned about. The question should probably have been tagged C though. \$\endgroup\$
    – Lundin
    Sep 8 at 21:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.