views:

289

answers:

6

Where can I find some good, proven guidelines or examples on writing extensible, modular, loosely-coupled code in C (if possible)?

Background of our problem is that we are maintaining large plain C, legacy code project for a low-cost microcontroller with limited computing and memory resources. Due to the fact that the system must be extremely reliable and the memory is rather limited, one of the first constraints is not to use dynamic memory allocation at all. All structures are mapped statically.

So we are looking for ways to make this code more maintainable and more modular. We are not interested in coding standards, but rather design suggestions. We have good coding conventions (naming, organizing code, SVN) so this is not a problem.

From what I've seen on the web (I may be wrong), it seems most of the programmers which program exclusively in plain C or assembler, at least in the uC/Embedded community, restrain from using anything more that plain procedural programming.

For example, we could get most of the OOP benefits and decoupling in plain C using callback functions, structs containing function pointers and similar stuff (it wouldn't require dynamic allocation, just passing around pointers to structs), but we would like to see if there are some proven methods already around.

Do you know of such resources, or have similar suggestions besides from "why don't you switch to C++ or other programming language"?

[Edit]

Thanks a lot for all the answers, I haven't had the time to examine them yet. Platform is 16-bit (XC166 or similar) uC, naked hw (no RTOS).

+3  A: 

A decent book on related subject - Small Memory Software: Patterns for systems with limited memory by James Noble and Charles Weir. They have free online version on the site.

Nikolai N Fetissov
Thanks, interesting link. I found, however, little useful information on fixed memory allocation and plain C patterns.
Groo
+2  A: 

You may want to have a look at the xDAIS algorithm standard. It was designed for DSP applications but the ideas can be adjusted to low resource embedded designs as well.

http://en.wikipedia.org/wiki/XDAIS_algorithms

In a nutshell: xDAIS is an OOP style interface convention not unlike COM for the C language. You have a fixed set of interfaces that a module may implement via a structure of function pointers.

The interfaces are strictly defined, so it is very easy to exchange components, stack them together to build higher level functionality and so on. The interfaces (and a code-checker) also make sure that all components remain separated. If the code-checker is used it is impossible to write a component that directly calls other components private functions.

Memory allocation is usually done at initialization time and under the control of the system designer (it's part of the main interface that all components must implement).

Static and dynamic allocation strategies are possible. You can even go dynamic without the risk of memory fragmentation because all components must be able to relocate themselves to different memory addresses.

The xDAIS standard defines a very lean OOP style mechanism for inheritance. This comes very handy for debugging and logging purposes. Having an algorithm that does funny things? Just add a simple single-file wrapper around an existing algorithm and log all calls to an UART or so. Due to the strictly defined interface there is no guesswork how a module works and how parameters are passed.

I've used xDAIS it in the past, and it works well. It takes a while to get used to it, but the benefits of the plug-and-play architecture and ease of debugging outweigh the initial effort.

Nils Pipenbrinck
Are there any other pointers for xDAIS? It looks to be very interesting and I have been looking for something like it for a long time.
Amigable Clark Kant
Sure. It's on on the ti.com website. Start here: http://focus.ti.com/lit/ug/spru424c/spru424c.pdf The IALG interface is the most important (especially the memory allocation part). You may want to ignore all the DMA related things and invent your own interfaces for your application (e.g. file or net access or whatever you want to do).
Nils Pipenbrinck
@Nils, thanks! It is always nice when there is a standard way of doing things. Especially when it seems to be a pretty good way. :-)
Amigable Clark Kant
Thanks, this really looks promising. All memory used for data and implementation can be allocated statically, which is exactly what I need. One more thing, which parts of the provided framework do you find useful in practice (wizards, code composer and other mentioned tools)? Do you use the entire set, or only follow the guidelines and have your own framework? Thanks again!
Groo
Don't use any of the tools unless your target platform happends to be one of the DSP's this standard was made for. That would be useless. Instead take a look how TI defined the ALG interface and use it as an inspiration to roll your own thing. You will for sure have different requirements than a typical DSP algorithm :-)
Nils Pipenbrinck
@Nils, I realized that after looking TIs stuff for a while. It would be nice if there were an open standard or library for efficient component-based programming in the embedded context.
Amigable Clark Kant
A: 

I will try to begin an answer here. If anything else comes to mind, I will get back here, because this problem is interesting. I will also monitor this question for other answers.

  • Separate logic and execution:

    Embedded systems can benefit from the same kind of separation of logic and I/O as large business applications.

    If, for example you are coding for some embedded device that reads values, interprets these and alter something based on these readings, you might want to separate the "logic" part completely from the part where you actually communicate with the network, the hardware, the user or whatever external entity.

    When you can describe the "rules" completely in some kind of memory structure or C code, without linking to anything but the message passing routines or similar, you have what I try to describe. In short, reducing side effects makes the code more modular.

  • I don't know if you are using threads or not, but either way proto threads offers a similar abstraction, less powerful than threads, but also a lot less likely to confuse the programmer.

  • Growing up on the Amiga, I have a hard time forgetting it. A ROM-able operating system, yet easily extended in RAM by loadable libraries. Heavy use of pointer passing made for both tight code and fast messages.

Reading another answer from Nils Pipenbrinck, his suggestion to use xDAIS looks to be a good (but far from only) way of implementing this. If most of your code is using some message convention like this, chances are your code is modular and maintainable.

I would also bring up running preprocessor or precompiler passes on the code before compiling for the target, but then we drift into a gray area ... this is almost like switching languages, and the requirement was C.

Amigable Clark Kant
+1  A: 

We are not using many small devices, but we do have some with memory constraints. We are allocating static buffers, but we found that sometimes dynamic memory allocation actually helps reducing the memory usage. We tightly control the heap size and allocation policy and have to check and handle out of memory conditions not as errors but as normal operation. E.g. we are out of memory, so we send out the data that we have, clear the buffers and resume operations where we left of.

Why do we not switch to C++? I would love to. We don't switch for mainly these reasons:

  1. Our code monkeys would not grok it, and are reluctant to learn.
  2. The C++ libraries are significantly larger (although we may be able to work around this one.)
  3. For those really small RTOS devices it's usually not necessary. For bigger devices, where we are running an embedded Linux, it would be nice.

HTH.

Fozi
Not exactly what we had in mind, but thanks for sharing the idea. Although, if you say "we send out the data, clean the buffers", it looks like you could do the same with a FIFO buffer (producer/consumer queue)? Getting the "Out of memory" error all the time during execution seems rather dangerous (but if it's proven and works reliably, then I guess you did it right). But data processing in our application must really be done in real time, without delays, so we avoid dynamic allocation and nondeterministic behavior (and we don't need to allocate anything dynamically, data size is fixed).
Groo
+3  A: 

We're in a similar situation. To address these concerns, we've implemented a build system that supports multiple implementations of desired interfaces (which implementation used is a function of the compilation target), and avoid use of API features that aren't included in the portable wrappers. The wrapper definition lives in a .h file that #include's the implementation-specific header file. The following mock-up demonstrates how we might handle a semaphore interface:

#ifndef __SCHEDULER_H
#define __SCHEDULER_H

/*! \addtogroup semaphore Routines for working with semaphores.
 * @{
 */

/* impl/impl_scheduler.h gets copied into place before any file using
 * this interface gets compiled. */
#include "impl/impl_scheduler.h"

/* semaphore operation return values */
typedef enum _semaphoreErr_e
{
    SEMAPHORE_OK = impl_SEMAPHORE_OK,
    SEMAPHORE_TIMEOUT = impl_SEMAPHORE_TIMEOUT
} semaphoreErr_e;

/*! public data type - clients always use the semaphore_t type. */
typedef impl_semaphore_t semaphore_t;

/*! create a semaphore. */
inline semaphore_t *semaphoreCreate(int InitialValue) {
  return impl_semaphoreCreate(InitialValue);
}
/*! block on a semaphore. */
inline semaphoreErr_e semaphorePend(semaphore_t *Sem, int Timeout) {
  return impl_semaphorePend(Sem, Timeout);
}
/*! Allow another client to take a semaphore. */
inline void semaphorePost(semaphore_t *Sem) {
  impl_semaphorePost(Sem);
}

/*! @} */

#endif

The public API is documented for use, and the implementation is hidden until compilation time. Using these wrappers also should not impose any overhead (though it might, depending on your compiler). There is a lot of purely mechanical typing involved, though.

Aidan Cully
Thanks, this seems like a simple and clean solution without introducing custom frameworks. Actual implementation is resolved during compile time, which prevents mistakes, although it doesn't leave the option of using custom strategies during runtime (if I got it right). It shouldn't be too difficult for developers to get used to the policy. One more question: How far do you actually do with this? Do you have all the parts of your system separated into such interfaces? And when you say "build system", you are referring to a build script which copies implementation files? Thanks again!
Groo
I think you've got it right... If we want to introduce custom strategies at run-time, we use other mechanisms (function pointers, and others). We don't have the entire system segregated this way - just the bits where we know we'd need different implementations. A HAL has one or more implementations that target different hardware the interface would support (like different interrupt controllers), and (hopefully) at least one implementation that can be targeted to the development PC, for better control / testing in that environment. Unit tests will use a different framework...
Aidan Cully
The "build system" is actually how the build gets run. It's a custom ruby script that supports DSLs (like one we have defining memory addresses, that generates output that can be used by .c or assembly files, and whose output can be used by the build system itself for building the MMU table, and the scatter.txt file used by the ARM linker), copies the .h files into place, compiles / links .c, .a, .s files, etc.. I'm generally pretty happy with the results, but if I had it to do over again, I'd look at scons very carefully.
Aidan Cully
A: 

You'd better be very sure that a fixed layout is what you want! Tearing it down and putting in a dynamic one could get very tricky!

I suggest the problems that any embedded framework are trying to manage are:

Calculating offsets to data

It should be possible to create a single struct for all memory but this _so_ doesn't feel like the right way to do it. C compilers are not usually asked to work with multi-megabyte structures and I get the feeling doing this is not very portable between compilers.

If structures are not used, then five sets of defines are needed, based on what is essentially a data schema:

  • sizes of simple types
  • offsets of fields within group types
  • sizes of group types
  • offsets of groups in runs of groups
  • size of runs of groups
  • (possibly also absolute addresses of runs of groups if performance dictates)

These defines have a tree-like dependency tree, that rapidly gets very complicated in raw C because types usually have to be packed/aligned eg in 4-byte clumps for optimal performance. The fully expanded defines can quickly end up more complex than some compilers are happy to process.

The easiest way to manage these issues in raw C projects is to calculate the offsets with a C program that is a "build tool" executable of the project and import them into the project as a .h file that contains explicit opffset numbers. With this approach a single macro taking a base address and relevant indices should be made available during the main compile for accessing each leaf of the data structure.

Avoiding function pointer corruption and maximising debugging productivity

If function pointers are stored in the object they are more vulnerable to data corruption leading to mysterious errors. The better way (for those memory ranges that contain different object types from time to time) is to store a vtable code in the object which is a lookup index into a set of function pointer sets.

The vtables can again be computed and generated as a .h file with #defines by a generator C program that is an executable "build tool".

A special constructor macro is required to write in the appropriate vtable id to initialise usage of an object.

Both of these problems are effectively well-solved already by for example the objective-C preprocessor (which will output raw C), but you can do it from scratch if you want to stay with a very small set of tools.

Allocating memory blocks to resources/tasks in the static memory structure

If you need to support multi-threading, associating short-lived dynamic tasks with particular indexes in the tree structure (nearest thing to allocating an object in the equivalent procedural/OO program) is perhaps best performed by trial-locking an arbitrary index, using for example an atomic increment (from zero with ==1 check) or mutex, then checking to see if the block is available, and if so, marking it as used, then unlocking the block.

If multi-threading support is not required then this is unnecessary; I suggest writing a custom framework for managing such resource allocation processes which can be run in either a multi-threaded or single-threaded mode, to allow the rest of the code base to be unconcerned with this topic, and to allow faster performance on single-threaded systems.

martinr
"No dynamic memory allocation" != "one gigantic structure describing all variables". In this sort of application, the target defines global variables, offsets to which are determined by the linker, not by you.
Aidan Cully
Oh - true! I grasped the wrong end of the stick. I was wrong
martinr