NASA lays out a set of rules in the paper “The Power of 10 Rules” that, when followed, help increase confidence in the correctness of software. This aids the development of safety-critical software systems by reducing ambiguity and complexity as much as possible. The rules are designed to make the software easier to understand, analyse, and verify safety-critical applications where failures can have severe consequences.
goto statements, setjmp or
longjmp constructs, or direct or indirect recursion.typedef
declarations. Function pointers are never permitted.When safety-critical software fails, real-world consequences are realised. Some notable failures include:
Standards and certification processes for safety-critical software are crucial to ensure correctness and reliable operation. Domain-specific standards such as DO-178C for aerospace, IEC 62304 for medical devices, and ISO 26262 for automotive software provide comprehensive guidelines, rules, and design processes across a system’s lifecycle, including requirements traceability, software design, coding practices, testing, and verification. Adherence to these standards is mandatory before a system can be certified for use in safety-critical contexts. Rigorous documentation, testing, and independent verification are required to ensure that the software meets the necessary safety requirements.
The importance of following these rules is best illustrated by comparing a standard, potentially unsafe implementation with one that prioritizes correctness.
#include <stdio.h>
#include <stdlib.h>
int *samples = NULL;
int idx = 0;
void process_sensor(void) {
while (1) {
int v;
if (scanf("%d", &v) != 1) break;
if (!samples) samples = malloc(sizeof(int) * 1000);
samples[idx++] = v;
if (idx > 16) {
goto report;
}
}
report:
long sum = 0;
for (int i = 0; i < idx; ++i) sum += samples[i];
double avg = (double)sum / idx;
printf("Avg: %f\n", avg);
}
int main() {
process_sensor();
free(samples);
return 0;
}The use of goto makes control flow harder to reason
about, while the unbounded loop risks resource exhaustion. Dynamic
memory allocation without a robust deallocation strategy can lead to
leaks. The function is also too long and mixes responsibilities.
Furthermore, variables are not scoped tightly, the index is unchecked,
and scanf return values are ignored - all of which
introduce significant risk.
#include <stdio.h>
#include <stdbool.h>
#include <stdint.h>
#define SAMPLE_COUNT 16
static int sensor_read(void) {
int v;
if (scanf("%d", &v) != 1) return INT32_MIN;
return v;
}
#define CHECK(cond, action) do { if (!(cond)) { action; } } while (0)
int compute_average(const int buf[], size_t n, double *out) {
CHECK(buf != NULL, return -1);
CHECK(out != NULL, return -1);
CHECK(n > 0, return -2);
long sum = 0;
for (size_t i = 0; i < n; ++i) {
sum += buf[i];
}
*out = (double)sum / (double)n;
return 0;
}
int main(void) {
int samples[SAMPLE_COUNT];
size_t count = 0;
for (size_t i = 0; i < SAMPLE_COUNT; ++i) {
int v = sensor_read();
if (v == INT32_MIN) {
break;
}
samples[count++] = v;
}
double avg;
int rc = compute_average(samples, count == 0 ? 1 : count, &avg);
if (rc != 0) {
fprintf(stderr, "compute_average failed (rc=%d)\n", rc);
return 1;
}
printf("Average over %zu samples: %.3f\n", count, avg);
return 0;
}No goto statements are used, creating a clearer control
flow. A fixed upper bound on the number of iterations is established by
SAMPLE_COUNT, ensuring that the loop cannot exceed a preset
upper bound resulting in resource exhaustion. Dynamic memory allocation
is avoided after initialization by using a fixed-size array
samples. Functions are short and focused, with each having
a single responsibility. Assertions are implemented using the
CHECK macro, which checks for anomalous conditions and
takes explicit recovery actions by returning error codes. Variables are
declared at the smallest possible scope, and all return values are
checked to ensure that errors are handled appropriately. The use of the
preprocessor is limited to a simple macro definition for assertions, and
pointer use is simple and straightforward. The code is written to be
compiled with warnings enabled, supporting static analysis and ensuring
that it adheres to good coding practices.
When compiling this code, the most pedantic warnings should be enabled with:
gcc -Wall -Wextra -pedantic -o safe_avg safe_avg.c