97

Are there any known design principles, best-practices and design patterns that one can follow while designing a C project? Or useful design principles for procedural (imperative) programming in general?

(I'm child of the 'object-oriented generation' and have to design a large C project for the first time)

Dimi
  • 981
  • 1
  • 7
  • 5
  • 1
    You may be interested in anwsers to this question: http://stackoverflow.com/questions/661307/recommendations-for-structuring-complex-applications-in-c – mouviciel Mar 22 '10 at 13:40
  • 7
    I did some internet and university library research, before I posted my question, and was definitely not overwhelmed with books about software design for C. I ask you for your favorite (not talking about general C books, not talking about coding conventions like meaningful variable names, but about a higher abstraction, software architecture level). Furthermore, I do not agree to your 'reliant on other people' reproach. You mean that each programmer should find out by himself about best practices and good design patterns? This is for sure a question on which other's experience has to be used. – Dimi Mar 22 '10 at 14:57
  • 2
    Sorry, Dimi, that wasn't about you in particular, and I wasn't very clear. This stuff used to be passed down as much by oral tradition as any other way: there wasn't a nominal set of official "Patterns", Jonathon's answer was what you'd find in the books, but *everyone* knew about information hiding. It seems the oral tradition is being lost, and many young programmers think that OOP invented the encapsulation and separation. This community seems to have less sense of its own history than I'd like to see. Thus my acknowledgment that I'm in grumpy old man territory. – dmckee --- ex-moderator kitten Mar 22 '10 at 15:37
  • 1
    I cannot share your retrospective view since just find my feet in the field, but I accept your suggestion. Thank you for expressing yourself more clearly, always of great value to get to read the view of an experienced person. I really appreciate your contribution. – Dimi Mar 22 '10 at 15:56
  • SEI CERT C Coding Standard provides [Good set of Rules and Common good practices](https://www.securecoding.cert.org/confluence/display/c/SEI+CERT+C+Coding+Standard) as well as things you should try to avoid using. – Rand0m Mar 02 '17 at 14:23

4 Answers4

68

Information hiding - as espoused by Parnas (Software Fundamentals).

Careful management of headers and visibility:

  • Everything in a source file that can be hidden from the outside world should be; only the documented external interface should be exposed.
  • Everything that is exposed is declared in a header.
  • That header is used where the functionality is needed (and where it is defined).
  • The header is self-contained - when you need it, you use it, and you don't have to fret about 'what other headers do I also have to include' because the header ensures it works by including anything it needs to make it work.
  • The header is self-protected - so it does not matter if it is included multiple times.

    #ifndef HEADER_H_INCLUDED
    #define HEADER_H_INCLUDED
    ...rest of header contents, including other #include lines if necessary
    #endif /* HEADER_H_INCLUDED */
    
  • Design sets of functions to work on 'objects' (usually structures) - and use those functions rather than poking around the innards of the structure in the code that is using it. Think of it as self-imposed encapsulation.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • Good point, thank you, Jonathan. Abstract data types are another good example of information hiding with a clean separation of usage and implementation (known external interface and unknown internal implementation). – Dimi Mar 22 '10 at 15:38
25

My three advices:

  • Write unit tests. They will help you zero in on a design that suites your problem as you go along. Much better than relying (solely) on pre-meditated thinking.
  • Have a memory leak detector (there are all sort of libraries out there) installed and running from day one. Have this library print out all leaks as soon as the program/tests exits. This will allow you to catch a leak as soon as you introduce it, thereby making its fixing much less painful.
  • Write OOP code in C. Not that difficult. While it is possible to emulate method overriding, I suggest that you start with emulation of simple objects. Even this simple mechanism can give you great mileage.

Here's an example:

typedef struct Vector {
  int size;
  int limit;
  int* ints; 
} Vector;

Vector* Vector_new() {
  Vector* res = (Vector*) malloc(sizeof(Vector));
  res->limit = 10;
  res->size = 0;
  res->ints = (int*) malloc(sizeof(int) * res.limit);

  return res;
}


void Vector_destroy(Vector* v) {
  free(v->ints);
  free(v);
}

void Vector_add(Vector* v, int n) {
  if(v->size == v->limit) {
    v->limit = v->limit * 2 + 10;
    v->ints = realloc(v->ints, v->limit);     
  }

  v->ints[v->size] = n;
  ++v->size;
}

int Vector_get(Vector* v, int index) {
  if(index >= 0 && index < v->size)
    return v->ints[index];

  assert false;
}
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
Itay Maman
  • 30,277
  • 10
  • 88
  • 118
22

There is a good, free, online book, titled Object-Oriented Programming With ANSI-C, which covers the topic of writing object-oriented code in C. A google search for "object-oriented C" also yields a number of other good examples and resources.

If your project is safety-critical, MISRA-C is a good set of rules. It is intended mostly for embedded c, but it can be useful in other areas as well.

I consider myself an OO coder, and I do a lot of work with embedded-C. The best advice I can give, especially for large projects, is not to overdo it. Creating a complete OO framework on top of ANSI C can be very tempting, but it takes a great deal of time and effort to get it right. The fancier you get, the more time you will spend debugging your framework instead of working on the real project. Approach the task with a clear head, and a good, solid grasp of YAGNI. Best of luck!

e.James
  • 116,942
  • 41
  • 177
  • 214
  • Thank you, e.James. I do not want to create an object-oriented framework on top of ANSI C, but look for special and appropriate procedural programming design principles. The MISRA-C hint is very useful, especially because it actually is an embedded project. I am going to take a closer look at it. – Dimi Mar 22 '10 at 14:27
  • Ah, the joys of embedded C. Don't forget that you have to declare your variables at the top of your function (or at the top of any `{ }` block). That one always bites me once or twice `:)` – e.James Mar 22 '10 at 16:44
8

OOP is a methodology not a technology. So my first bit of advice is stop thinking of it as procedural programming.

To e.James's point, you don't want to try and re-create an object-oriented language or pretend that you have the capabilities thereof. You can still do all the right things by clinging to a few simple principles:

  1. Test drive everything.
  2. Find what varies and encapsulate it.
  3. Design to interfaces.