views:

183

answers:

5

Now that I got my head wrapped around the 'C' language to a point where I feel proficient enough to write clean code, I'd like to focus my attention on project architecture guidelines. I'm looking for a good resource that coves the following topics:

  1. How to create an interface that promotes code maintainability and is extensible for future upgrades.
  2. Library creation guidelines. Example, when should I consider using static vs dynamic libraries. How to properly design an ABI to cope with either one.
  3. Header files: what to partition out and when. Examples on when to use 1:1 vs 1:many .h to .c
  4. Anything you feel I missed but is important when attempting to architect a new C project.

Ideally, I'd like to see some example projects ranging from small to large and see how the architecture changes depending on project size, function or customer.

What resource(s) would you recommend for such topics?

+3  A: 
  1. Separate presentation code from logic. That is very important.
  2. Static if they're used only in your project or in few binaries, dynamic when used many times (saves very much space).
  3. Whenever code is used more than once, split it off into a header.

Those are a few of my known tips I can give you.

Delan Azabani
+7  A: 

Whenever I got serious writing C code, I had to emulate C++ features in it. The main stuff worth doing is:

  • Think of each module like a class. The functions you expose in the header are like public methods. Only put a function in the header if it part of the module's needed interface.

  • Avoid circular module dependencies. Module A and module B should not call each other. You can refactor something into a module C to avoid that.

  • Again, following the C++ pattern, if you have a module that can perform the same operations on different instances of data, have a create and delete function in your interface that will return a pointer to struct that is passed back to other functions. But for the sake of encapsulation, return a void pointer in the public interface and cast to your struct inside of the module.

  • Avoid module-scope variables--the previously described pattern will usually do what you need. But if you really need module-scope variables, group them under a struct stored in a single module-scope variable called "m" or something consistent. Then in your code whenever you see "m.variable" you will know at a glance it is one of the module-scope structs.

  • To avoid header trouble, put #ifndef MY_HEADER_H #define MY_HEADER_H declaration that protects against double including. The header .h file for your module, should only contain #includes needed FOR THAT HEADER FILE. The module .c file can have more includes needed for the compiling the module, but don't add those includes into the module header file. This will save you from a lot of namespace conflicts and order-of-include problems.

Erik Hermansen
Good list. The other thing to do with the headers is ensure that each one is self-contained - it includes any headers it needs, so you can include it without having to worry about what else you need.
Jonathan Leffler
Re: void pointers in the public interface - I much prefer to use typedef'ed opaque structures. That way you don't have to futz around with re-casting, and it provides the same sort of implementation protection. http://en.wikipedia.org/wiki/Opaque_pointer#C
Matt B.
Agreed with Matt B. Don't return void *, use nice typedef'd abstract data types. See my example here in the "Implement a class in your favorite language" question: http://stackoverflow.com/questions/2702450/implement-a-simple-class-in-your-favorite-language/2771898#2771898
Mike Weller
This is a very well thought out answer and I although I was looking for more of a book recommendation (like C Interfaces and Implementations), you look like you could use the points.
SiegeX
+4  A: 

Namespace cleanliness - especially important with libraries. Prefix your public functions with the name of the library, in some fashion. If your library was called 'happyland', make functions such as "happyland_init" or even "hl_init".

This goes for the use of static. You will write functions which are specialized - hide them from other modules by using static liberally.

For libraries, reentrant is also critical. Do not depend on global state which is not compartmentalized (you can have a typedef struct token to keep this state if required).

Yann Ramin
+5  A: 

The truths about architecting systems are timeless, and they cross language boundaries. Here is a little advice focused on C:

  • Every module hides a secret. Build your system around interfaces that hide information from their clients. C's only type-safe information-hiding construct is the pointer to an incomplete structure. Learn it thoroughly and use it often.

  • One interface to an implementation is a good rule of thumb. (Interface is .h, implementation is .c.) Sometimes you will want to provide two interfaces that relate to the same implementation: one that hides the representation and one that exposes it.

  • You'll need naming conventions.

A superb model of how to handle these kinds of problems in C is Dave Hanson's C Interfaces and Implementations. In it you will get to see how to design good interfaces and implementations, and how one interface can build on another to form a coherent library. You will also get an excellent set of starter interfaces you can use in your own applications. For someone in your position, I cannot recommend this book too highly. It is an archetype of well-architected systems in C.

Norman Ramsey
+1 for CII. Very good book, in more ways than one.
Yann Ramin
+2  A: 

How to create an interface that promotes code maintainability and is extensible for future upgrades.

By exposing as few implementation details as possible. For example,

  • Use opaque pointers.
  • If you need a "private" function, declare it static, and don't put it in the .h file.
dan04