81

I have little more than beginner-level C skills and would like to know if there are any de facto "standards" to structure a somewhat complex application in C. Even GUI based ones.

I have been always using the OO paradigm in Java and PHP and now that I want to learn C I'm afraid that I might structure my applications in the wrong way. I'm at a loss on which guidelines to follow to have modularity, decoupling and dryness with a procedural language.

Do you have any readings to suggest? I couldn't find any application framework for C, even if I don't use frameworks I've always found nice ideas by browsing their code.

Stephen
  • 1,528
  • 1
  • 17
  • 37
  • 3
    The Unix philosophy is pretty useful for organizing large projects: [http://www.faqs.org/docs/artu/ch01s06.html](http://www.faqs.org/docs/artu/ch01s06.html) – newDelete Mar 18 '12 at 04:13

9 Answers9

53

The key is modularity. This is easier to design, implement, compile and maintain.

  • Identify modules in your app, like classes in an OO app.
  • Separate interface and implementation for each module, put in interface only what is needed by other modules. Remember that there is no namespace in C, so you have to make everything in your interfaces unique (e.g., with a prefix).
  • Hide global variables in implementation and use accessor functions for read/write.
  • Don't think in terms of inheritance, but in terms of composition. As a general rule, don't try to mimic C++ in C, this would be very difficult to read and maintain.

If you have time for learning, take a look at how an Ada app is structured, with its mandatory package (module interface) and package body (module implementation).

This is for coding.

For maintaining (remember that you code once, but you maintain several times) I suggest to document your code; Doxygen is a nice choice for me. I suggest also to build a strong regression test suite, which allows you to refactor.

mouviciel
  • 62,742
  • 10
  • 106
  • 135
34

It's a common misconception that OO techniques can't be applied in C. Most can -- it's just that they are slightly more unwieldy than in languages with syntax dedicated to the job.

One of the foundations of robust system design is the encapsulation of an implementation behind an interface. FILE* and the functions that work with it (fopen(), fread() etc.) is a good example of how encapsulation can be applied in C to establish interfaces. (Of course, since C lacks access specifiers you can't enforce that no-one peeks inside a struct FILE, but only a masochist would do so.)

If necessary, polymorphic behaviour can be had in C using tables of function pointers. Yes, the syntax is ugly but the effect is the same as virtual functions:

struct IAnimal {
    int (*eat)(int food);
    int (*sleep)(int secs);
};

/* "Subclass"/"implement" IAnimal, relying on C's guaranteed equivalence
 * of memory layouts */
struct Cat {
    struct IAnimal _base;
    int (*meow)(void);
};

int cat_eat(int food) { ... }
int cat_sleep(int secs) { ... }
int cat_meow(void) { ... }

/* "Constructor" */
struct Cat* CreateACat(void) {
    struct Cat* x = (struct Cat*) malloc(sizeof (struct Cat));
    x->_base.eat = cat_eat;
    x->_base.sleep = cat_sleep;
    x->meow = cat_meow;
    return x;
}

struct IAnimal* pa = CreateACat();
pa->eat(42);                       /* Calls cat_eat() */

((struct Cat*) pa)->meow();        /* "Downcast" */
aklektik
  • 789
  • 1
  • 10
  • 29
j_random_hacker
  • 47,823
  • 9
  • 95
  • 154
  • 11
    A pure C coder would get lost reading this code... – mouviciel Mar 19 '09 at 08:06
  • 15
    @mouviciel: Rubbish! Most C coders understand function pointers (or at least they should), and there's nothing really going on beyond that here. On Windows at least, device drivers and COM objects both provide their functionality this way. – j_random_hacker Mar 19 '09 at 09:29
  • 8
    My point is not about incompetency, it's about unneeded complication. Function pointers are common for a C coder (e.g., callbacks), inheritance isn't. I prefer that a C++ coder coding in C use its time to learn C than to build pseudo C++ classes. That said, your approach may be useful in some cases. – mouviciel Mar 19 '09 at 13:03
  • 3
    Actually you can even *simulate* access specifiers using the C++ pimpl idiom. If the *private* members of a type are encapsulated in a type that is only visible in the implementation (aka "the .c file"), users of the interface will have a hard time changing them (of course, they can write rubbish to the pimpl pointer, if they want to screw you **intentionally**, but you can do the same in C++). – bitmask Sep 14 '11 at 00:27
  • 2
    Downvoter: care to comment? – j_random_hacker Oct 17 '13 at 16:48
  • I've got a stupid question here, with your code @j_random_hacker, in "methods" (cat_eat, cat_sleep ...), you cannot access to Cat other struct properties ? If you want to, you have send it by ref for each methods ? If so, it's useless :/ basic functions + struct (like FILE and fopen, ftell, fclose ...) do the job, and it is more comprehensive (and maybe more efficient ?) – soywod Oct 28 '16 at 17:10
  • @Soywod: I don't follow. Of course you can access other properties in struct Cat; what prevents this? – j_random_hacker Oct 28 '16 at 20:04
  • @j_random_hacker let's say you have the `weight` property in the `Cat` struct. In the `cat_eat` method, how can you increase the weight of the current Cat ? (like a `this` keyword) – soywod Oct 29 '16 at 19:51
  • @Soywod: Although I didn't write it in the example code, I think you would normally declare all methods in the interface to take an extra pointer-to-base parameter, which functions like `this`, e.g. `int (*eat)(struct IAnimal *_this, int food);`. Then in a particular derived class's implementation of that method, you would immediately downcast that pointer-to-base to a pointer-to-derived to access derived-class fields. – j_random_hacker Oct 29 '16 at 20:40
  • 1
    @j_random_hacker: Shouldn't CreateACat() return struct Cat* ? I'm surprised no one else has mentioned this, which leads me to think there's something I don't understand? – aklektik Apr 14 '21 at 14:02
  • @aklektik: You're right, and I'm also surprised! Just an oversight, thanks for the edit :) – j_random_hacker Apr 15 '21 at 02:36
16

All good answers.

I would only add "minimize data structure". This might even be easier in C, because if C++ is "C with classes", OOP is trying to encourage you to take every noun / verb in your head and turn it into a class / method. That can be very wasteful.

For example, suppose you have an array of temperature readings at points in time, and you want to display them as a line-chart in Windows. Windows has a PAINT message, and when you receive it, you can loop through the array doing LineTo functions, scaling the data as you go to convert it to pixel coordinates.

What I have seen entirely too many times is, since the chart consists of points and lines, people will build up a data structure consisting of point objects and line objects, each capable of DrawMyself, and then make that persistent, on the theory that that is somehow "more efficient", or that they might, just maybe, have to be able to mouse over parts of the chart and display the data numerically, so they build methods into the objects to deal with that, and that, of course, involves creating and deleting even more objects.

So you end up with a huge amount of code that is oh-so-readable and merely spends 90% of it's time managing objects.

All of this gets done in the name of "good programming practice" and "efficiency".

At least in C the simple, efficient way will be more obvious, and the temptation to build pyramids less strong.

Mike Dunlavey
  • 38,662
  • 12
  • 86
  • 126
11

The GNU coding standards have evolved over a couple of decades. It'd be a good idea to read them, even if you don't follow them to the letter. Thinking about the points raised in them gives you a firmer basis on how to structure your own code.

  • 4
    Not everybody likes them, from http://lxr.linux.no/linux+v2.6.29/Documentation/CodingStyle: "First off, I'd suggest printing out a copy of the GNU coding standards, and NOT read it. Burn them, it's a great symbolic gesture". I have not read them in many years, but Linus have some valid objections. – hlovdal Apr 13 '09 at 11:25
  • 1
    @hlovdal: Of course not everyone likes any one particular coding standard, that's why there are more than one standard for similar use cases. The important part is that your consistent within your own projects, that at least some standard is followed, rather than de facto ad hoc inconsistency. – J. M. Becker Apr 22 '14 at 15:13
4

If you know how to structure your code in Java or C++, then you can follow the same principles with C code. The only difference is that you don't have the compiler at your side and you need to do everything extra carefully manually.

Since there are no packages and classes, you need to start by carefully designing your modules. The most common approach is to create a separate source folder for each module. You need to rely on naming conventions for differentiating code between different modules. For example prefix all functions with the name of the module.

You can't have classes with C, but you can easily implement "Abstract Data Types". You create a .C and .H file for every abstract data type. If you prefer you can have two header files, one public and one private. The idea is that all structures, constants and functions that need to be exported go to the public header file.

Your tools are also very important. A useful tool for C is lint, which can help you find bad smells in your code. Another tool you can use is Doxygen, which can help you generate documentation.

kgiannakakis
  • 96,871
  • 26
  • 155
  • 191
4

Encapsulation is always key to a successful development, regardless of the development language.

A trick I've used to help encapsulate "private" methods in C is to not include their prototypes in the ".h" file.

Nate
  • 16,729
  • 25
  • 68
  • 93
3

I'd suggets you to check out the code of any popular open source C project, like... hmm... Linux kernel, or Git; and see how they organize it.

Ivan Krechetov
  • 17,548
  • 8
  • 45
  • 58
3

The number rule for complex application: it should be easy to read.

To make complex application simplier, I employ Divide and conquer.

MrValdez
  • 8,182
  • 10
  • 51
  • 77
2

I would suggest reading a C/C++ textbook as a first step. For example, C Primer Plus is a good reference. Looking through the examples would give you and idea on how to map your java OO to a more procedural language like C.

Tosa
  • 21
  • 1