15

One aspect where C shows its age is the encapsulation of code. Many modern languages has classes, namespaces, packages... a much more convenient to organize code than just a simple "include".

Since C is still the main language for many huge projects. How do you to overcome its limitations?

I suppose that one main factor should be lots of discipline. I would like to know what you do to handle large quantity of C code, which authors or books you can recommend.

tshepang
  • 12,111
  • 21
  • 91
  • 136
SystematicFrank
  • 16,555
  • 7
  • 56
  • 102
  • A similar post, not specifically for C, but containing good general advice: http://stackoverflow.com/questions/2025733/coding-pratice-what-are-your-thoughts-on-a-1-7-million-loc-project – Péter Török Apr 28 '10 at 19:38

5 Answers5

20
  • Separate the code into functional units.
  • Build those units of code into individual libraries.
  • Use hidden symbols within libraries to reduce namespace conflicts.

Think of open source code. There is a huge amount of C code in the Linux kernel, the GNU C library, the X Window system and the Gnome desktop project. Yet, it all works together. This is because most of the code does not see any of the other code. It only communicates by well-defined interfaces. Do the same in any large project.

Zan Lynx
  • 53,022
  • 10
  • 79
  • 131
3

Some people don't like it but I am an advocate of organzing my structs and associated functions together as if they are a class where the this pointer is passed explicitly. For instance, combined with a consistent naming convention to make the namespace explicit. A header would be something like:

typedef struct foo {
  int x;
  double y;
} FOO_T

FOO_T * foo_new();

int foo_set_x(FOO_T * self, int arg1);

int foo_do_bar(FOO_T * self, int arg1);

FOO_T * foo_delete(FOO_T * self);

In the implementation, all the "private" functions would be static. The downside of this is that you can't actually enforce that the user not go and muck with the members of the struct. That's just life in c. I find this style though makes for nicely reusable C types.

paercebal
  • 81,378
  • 38
  • 130
  • 159
frankc
  • 11,290
  • 4
  • 32
  • 49
  • 2
    No problem with the style, but I'd much prefer `FOO_T *x; x->x = 1;` over `FOO_T *x; foo_set_x(x, 1);`. One is much clearer to a C programmer. (And you can enforce that the user not go and muck with the members of a `struct` by not giving them a definition. Just say `typedef struct foo FOO_T` and then they can use pointers to your type without seeing inside.) – Chris Lutz Apr 28 '10 at 20:33
  • I'd agree with Chris - either you provide a definition for the struct, and expect client code to mutate its members, or you use the pointer to a typedef idiom and hide the contents. – Pete Kirkham May 19 '10 at 22:08
2

A good way you can achieve some encapsulation is to declare internal methods or variables of a module as static

Andres
  • 3,324
  • 6
  • 27
  • 32
1

As Andres says, static is your friend. But speaking of friends... if you want to be able to separate a library in two files, then some symbols from one file that need to be seen in the other can not be static. Decide of some naming conventions: all non-static symbols from library foo start with foo_. And make sure they are always followed: it is precisely the symbols for which it seems constraining ("I need to call it foo_max?! But it is just max!") that there will be clashes.

As Zan says, a typical Linux distribution can be seen as a huge project written mostly in C. It works. There are interfaces, and large-subprojects are implemented as separate processes. An implementation in separate processes helps for debugging, for testing, for code reuse, and it provides a second hierarchy in addition to the only one that exists at link level. When your project becomes large enough, it may start to make sense to put some of the functionalities in separate processes. Something already as specialized as a C compiler is typically implemented as three processes: pre-processor, compiler, assembler.

Pascal Cuoq
  • 79,187
  • 7
  • 161
  • 281
0

If you can control the project (e.g. in-house or you pay someone else to do it) you can simply set rules and use reviews and tools to enforce them. There is no real need for the language to do this, you can for instance demand that all functions usable outside a module (=a set of files, don't even need to be a separate) must be marked thus. In effect, you would force the developers to think about the interfaces and stick with them.

If you really want to make the point, you could define macros to show this as well, e.g.

#define PUBLIC 
#define PRIVATE static

or some such.

So you are right, discipline is the key here. It involves setting the rules AND making sure that they are followed.

Makis
  • 12,468
  • 10
  • 62
  • 71
  • public and private in most OO languages do not mean the same as static and auto. – Pete Kirkham May 19 '10 at 22:10
  • I agree, discipline is important and important steps to achieve it are rules, reviews and tools as mentioned but I think re-writes are good for example-setting when rules are not followed. Early reviews (when code hasn't been completed) could lower the extra cost incurred for re-writes. The re-write decision could be formally decided by someone "high up" and further underline that rules are to be followed. – Olof Forshell Feb 26 '11 at 00:53
  • As long as `PRIVATE` isn't used in a header included by multiple files... :-) – paercebal May 11 '11 at 20:10