Welcome to Duncan White's Practical Software Development (PSD) Pages.

I'm Duncan White, an experienced and professional programmer, and have been programming for well over 30 years, mainly in C and Perl, although I know many other languages. In that time, despite my best intentions:-), I just can't help learning a thing or two about the practical matters of designing, programming, testing, debugging, running projects etc. Back in 2007, I thought I'd start writing an occasional series of articles, book reviews, more general thoughts etc, all focussing on software development without all the guff.

See all my Practical Software Development (PSD) Pages

picture of D. White


Individual Safety Tips or "Shared Norms" of C Programming?

Background to this Draft Article

Following a discussion on the "Plain Ordinary C" LinkedIn group, I started wondering whether C Programmers (or programmers more generally) develop a shared sense of normal and safe ways of programming, rather in the sense of "Social Norms" in psychology - heuristics that help drive behaviour in specialist groups, and avoid dangerous behaviours etc. A less highbrow term for these heuristics and Norms might be safety tips. Such practical safety tips as I have in mind might alert you intuitively to something "not being right" just because it's very abnormal, and by doing so keep you in the safe parts of C, and away from dangerous less explored areas, once marked "Here be Dragons" on nautical maps.

The specific trigger for these thoughts was a C newbie, writing a recursive call to main(). In fact, worse than that, main() called function f() which called main() again, i.e. it was Mutual Recursion involving main(). This seemed so weird and evil to me that I knew it was a very confused and bad example - before even adding in the fact that the recursive calls had no conditions, so it was an infinite mutual recursion that would not terminate until stack overflow occurred. So Do not call main(): that's the runtime system's job became my first safety tip - since reworded as Thou Shalt Not call main().

Having thought about this for a few days, I wrote the initial version of this document. Then I started a fresh discussion on the "Plain Ordinary C" LinkedIn group to discuss this idea and to solicit new tips (and revisions to the tips herein). Note that this is the first time that I've gone public with one of these articles before I judge that it's finished, this is a wisdom of crowds experiment for me. So far, the LinkedIn discussion has had over 400 (overwhelmingly positive) comments in about 3 weeks, and I have incorporated many suggestions into this article with full attribution - and the comments are still coming in, in fact there's a growing backlog of suggestions to incorporate! I'd like to thank everyone on the LinkedIn who have made contributions to this article, I'm extremely grateful for all the suggestions, disagreements and arguments, this article is gradually improving through their help.

ANSI C: Safety, Danger and Power

Now, just how dangerous is modern C? C used to be widely viewed as a powerful language with fewer-than-average protections against common mistakes made by unwary programmers. It used to be said that (for example): "Writing in C is like running a chainsaw with all the safety guards removed" (attributed to Bob Gray).

Howard Brodale of LinkedIn makes the important point that C isn't as dangerous as it once was, saying today's C compilers now hold our hands much more with prototypes and better parameter datatype checking. I completely agree with Howard that C is much less dangerous now than it once was, but I should add that C still contains many pitfalls, and the purpose of this article is to outline practical tips to avoid many of the remaining dangers in modern ANSI C.

Jack Purdum of LinkedIn adds that danger is not necessarily a bad thing, pointing out that all C programmers have implicitly agreed to trade some elements of safety (eg, array boundary checking, pointer confusions) for the performance improvements and economy of expression. I agree that (counterintuitively) C's dangers are a good thing - one reason why we all love C so much and continue to use it is because it occupies a sweet spot in which some safety is traded off for a lot of efficiency.

Safety Tips - what do I mean?

As a first step, before we can judge whether useful "Shared Norms" exist in C programming we need to see some individual C programmers collections of personal "safety tips". So, to get the ball rolling, here's my collection, classified into several areas:

Simplicity and clarity

Note that all these safety tips are guidelines rather than strict rules - as in Bill Murray's quote from Ghostbusters, actually, it's more of a guideline than a rule. As a professional programmer, you should not only have the freedom to choose which safety tips you like and don't like in general, but you should also keep the freedom to break your own rules in a specific case. Until you're replaced by a AI - you are the expert. Trust yourself. When you do deliberately break one of your own rules, this should be surprising to you - so it's worth adding a comment saying why you did. Some future maintainer of your code - you in the future, for instance - might thank you for it.

What sort of rule-breaking might we want to do? for example, we talked about favouring double over float above. But a float typically occupies half as much memory as a double. If you're working on a memory constrained system such as an embedded system, and storing millions of floating point values, declaring a large array of float instead of double may make the difference between your code fitting in available memory or not!

Avoid Undefined Behaviour and Gotchas

ANSI C clearly defines certain things as unspecified, undefined or implementation-defined behaviour. To quote the much missed Douglas Adams: C has rigidly defined areas of doubt and uncertainty. Do your best to avoid these. There are too many to list (eg. order of evaluation of parameters and expressions with side-effects) - note that the CERT Secure C Coding and the MISRA-C-2012 guides go into a lot of them. But here are a few positive tips worth mentioning:

Pointers and Storage Management

One of the most complex and error-prone parts of C programming is using pointers safely, getting memory allocation and storage management right. Here are a few safety tips in this area:

Functions

Separate Compilation

C has always supported compilation of separate C source files or compilation units, and provides external declarations, header files and linking support to glue compilation units together. However, these low-level facilities are so flexible that they allow you to get really confused, encounter linking errors etc. To avoid most of these problems, a set of conventions have emerged over time, enabling you to use separate compilation in a highly standardised and structured way - specifically, to achieve modular programming in C. Before we get to that, there is an important distinction we need to make, leading to our next tip:

Understand the difference between definitions and declarations: An amazing proportion of C programmers use the terms definition and declaration interchangeably in C. Declaration is the more general term, a definition is a special kind of a declaration - we'll see the difference later. For local variables and function parameters there's no difference - they're all declarations. But for global variables and functions (what the standard calls external linkage variables and functions) declarations and definitions are different:

Ok, so what's the difference?

Modular Programming in C

The above section really defines all the separate compilation rules that exist in C. As long as every C source file contains a definition of each type, public function and global variable that other C files should be able to use, and declarations of every public function or global variable belonging to another C file that it wants to use, it will compile and link successfully. However, we pointed out the fragility - that if any declaration is out of sync with the definition, then the program will not work correctly, parameters or return values with be corrupted in some complex way. Not only is such separate compilation code painful to write and maintain, but debugging and fixing such declaration/definition mismatch problems when they occur is horribly difficult.

Hence techniques have emerged that use C's separate compilation facilities in a standardised way to provide modular programming in C. So, our next tip is: Learn and use one of the techniques to provide modular programming in C: because the raw separate compilation rules are simply too unsafe to deal with. There are several such techniques, which vary slightly among themselves. It almost doesn't matter which specific technique or variation you use - what's important is that you pick one of them and use it.

The Common Header File

Modules: a .c file and a .h file

Module Interface Dependencies: to use Include Guards or not

Interface Afterthoughts

General Tips

Making it run fast: Optimization and Profiling

Final Words

Ok, that's the end (for now) of my personal list of safety tips for C programming. Given the shared nature of the discussion on LinkedIn, it seems appropriate that I should not have the last word. Chris Ryan summed up quite a lot of my thoughts on the role of simplicity and clarity in building software:

Simple engineering is just easier to understand.
You are less likely to make mistakes if your code and your algorithms are simple.
Simpler code is also normally much leaner and faster.
Simple code is normally faster to write, debug and maintain.
Simple code is also easier for the compiler to optimize

Thanks very much Chris, I couldn't have said it better myself! (Ok, so I did have the last word:-))


d.white@imperial.ac.uk
Back to PSD Top
Written: August-September 2015