[<<][meta][>>][..]
Fri Jul 24 15:08:00 CEST 2009

How to use C

An attempt to describe my C programming style.

The basic idea is: C isn't too bad as long as you get a hold on the
memory management.  Keep data structures simple and as much as
possible use stateless code with locally managed data (C-stack).

Outside of that: try to generate C from a higher level description.


  - Avoid malloc: write _functional_ C code where possible (allocate
    data on the stack) and try to use a garbage collector or gc +
    embedded scripting language for representing long-lived data.
    This greatly simplifies interactive testing in gdb.

    My experience is that in a low-level project, usually a lot of
    data doesn't need malloc() for intermediate representation.  The
    part that does need persistence can usually be structured in such
    a way that it can be queried by stateless code without the need to
    transform it into alternative long-lived (cached) representations.

  - If you need malloc, try to use _linear_ data structures: make
    everything look like a tree or a directed graph such that
    con(des)struction becomes simpler.  Code that is looking at
    inconsistent data because it's still building it or taking it
    apart _really_ needs to be kept minimal.

  - Use _comprehensions_ (iterator macros) where you would use
    iterator objects or scheme-like closures.  The idea is that
    `downward closures' are free in C, so using them is often simpler
    than writing iterator objects.

  - Use _single assigment_ variables.  Assign to variables at their
    declaration point, and declare a new name for every intermediate
    result.  The compiler is smart enough to optimize this.  In fact:
    the internal representation in GCC is exactly this!

  - Write pretty-printing routines that print data structures, but
    have no other side-effects.  Very useful in gdb.

  - Add a TRAP = kill(getpid(), SIGTRAP) routine at all places where
    you didn't implement something, instead of a print statement that
    reads "not implemented".  Then when running in the debugger, you
    can type it in directly at the trap point and recompile.

In a sense, this advice centers around a `functional' approach to
programming, where the ability to form genuine long-lived lexical
closures is forsaken for a C-stack approximation using a few simple
tricks.

If you write code that doesn't do anything except translating
descriptive data structures into more specific ones that are used by
your application: use an asynchrounous garbage collector, and manually
call it when you have the specific data.  Realize that this
configuration part is really a compiler.  Don't write a compiler
without GC.  Alternatively: generate a simple data structure or a C
program using a HLL.

Note: these ideas about data structures of course do not apply to
problems that are inherently `algorithmic' or (mutable) data-structure
centric.




A different point: don't abuse the C preprocessor.  Essentially this
means that expansions shouldn't go to deep, since that leads to
hard-to-debug code.  Factor out everything that can go into an inline
function, and put the stuff you can't do in that way in a wrapper
macro.  The type checker is your friend whenever you try to change
something.  Macros can provide:

  - implicit bindings or context, i.e. the "current" object

  - abstractions over data and function definitions

  - abstractions over control structures

  - abstractions over (parts of) variable and function names




[Reply][About]
[<<][meta][>>][..]