53

Coming from a OO background (C#/java) I'm looking for resources to learn how to design pure C programs well.

Whilst i'm familiar with the syntax of C, and i can write small programs, i'm unsure of the approach to take for larger applications, and what techniques to employ. Anything you guys can recommend.

EDIT: I'm happy to completly abandon OO for the purposes of programming in C, my interest is in learning how to structure a program without OO, I want to learn about good ways of designing programs in procedural languages such as C.

K M
  • 2,924
  • 3
  • 19
  • 17
  • 8
    Learning C is probably one of the best things you can do to really expand your skill set. I know that's probably not what a lot of "modern" programmers are used to hearing these days, but it's true. I think your desire to learn it is a really smart move. – Dan Moulding May 29 '09 at 19:06
  • Awesome question, i'm in the same position. C itself is rather small but when writing larger applications, i too am stuck howto design them... – Sander Versluys Jan 03 '11 at 10:28

7 Answers7

55

This posting has a list of UNIX books which includes most of the classic C/Unix works. For C programming on Windows, Petzold's Programming Windows is probably the best start.

For C program design, some of the UNIX programming books will tell you snippets but I'm not aware of a 'C program architecture' book.

If you're used to java, some tips for C programming are:

  1. Make use of stack. Often when you call a procedure you will want to have variables allocated in the caller's stack frame and pass pointers to them into the procedure you want to call. This will be substantially faster than dynamically allocating memory with malloc() and much less error-prone. Do this wherever appropriate.

  2. C doesn't do garbage collection, so dynamically allocating data items is more fiddly and you have to keep track of them to make sure they get freed. Variables allocated on the stack (see 1) are more 'idiomatic' where they are applicable. Plus, you don't have to free them - this is a bonus for local variables.

  3. Apropos of (2), consider an architecture where your functions return a status or error code and pass data in and out using the stack as per (1).

  4. Get to know what setjmp() and longjmp() do. They can be quite useful for generic error handler mechanisms in lieu of structured exception handling functionality.

  5. C does not support exceptions. See (3).

  6. Lint is your friend. Splint is even friendlier.

  7. Learn what the preprocessor does and what you shouldn't do with it even if you can.

  8. Learn the ins and outs of endian-ness, word alignment, pointer arithmetic and other low-level architectural arcana. Contrary to popular opinion these are not rocket science. If you're feeling keen, try dabbling in assembly language and get a working knowledge of that. It will do much for your understanding of what's going on in your C program.

  9. C has no concept of module scope, so plan your use of includes, prototype declarations, and use of extern and static to make private scopes and import identifiers.

  10. GUI programming in C is tedious on all platforms.

  11. Apropos of (10) learn the C API of at least one scripting language such as Tcl, Lua or Python. In many cases, the best use of C is as a core high-performance engine on an application that is substantially written in something else.

  12. The equivalent of a constructor is an initializing function where you pass in a pointer to the item you want set up. Often you can see this in the form of a call to the function that looks like setup_foo(&my_foo). It's better to separate allocation from initialising, as you can use this function to initialise an item you have allocated on the stack. A similar principle applies to destructors.

  13. Most people find Hungarian notation about as readable as written Hungarian. The exception to this is native Hungarian speakers, who typically find Hungarian notation about as legible as Cuneiform.. Unfortunately, Hungarian notation is widely encountered in Windows software and the entire Win32 API uses it, with the expected effects on the legibility of software written on this platform.

  14. C/Unix books, even really good ones like the ones written by the late W Richard Stevens tend to be available secondhand quite cheaply through Amazon marketplace. In no particular order, get a copy of K&R, Stevens APUE and UNP 1 & 2, the Dragon book, Rochkind, Programming Pearls, Petzold and Richter (if working on Windows) and any of the other classic C/Unix works. Read, scribble on them with a pencil and generally interact with the books.

  15. There are many, many good C/Unix programming resources on the web.

  16. Read and understand the Ten Commandments of C Programming and some of the meta-discussion as to the why's and wherefores behind the commandments. This is showing its age to a certain extent, although most of it is still relevant and obscure compilers are still quite common in the embedded systems world.

  17. Lex and Yacc are your friend if you want to write parsers.

  18. As Navicore points out below (+1), Hanson's 'C Interfaces and Implementations' is a run-down on interface/implementation design for modular architecture with a bunch of examples. I have actually heard of this book and heard good things about it, although I can't claim to have read it. Aside from the C idioms that I've described above, this concept is arguably the core of good procedural design. In fact, other procedural languages such as Modula-2 actually make this concept explicit in their design. This might be the closest thing to a 'C Program Architecture' book in print.

  19. Read the C FAQ.

Abhishek
  • 13
  • 3
ConcernedOfTunbridgeWells
  • 59,622
  • 15
  • 138
  • 193
  • 2
    These are definitely some good bullet points. I'm surprised to see setjmp and longjmp mentioned so prominently. While powerful, use of these two should be pretty rare, no? If you think abuse of goto is bad (most people seem to), imagine abuse of setjmp and longjmp (yikes!). As for assmebly, I'd add that it's important to not just dabble in writing your own assembly, but also to analyze the assembly generated by the C compiler to get an understanding of what's really going on. – Dan Moulding May 29 '09 at 19:03
  • 1
    Setjmp does a bit more than goto. It squirrels away the local context of the function. You would normally use it to build generic error handlers in lieu of a structured exception mechanism. The advice is to understand how this mechanism works. – ConcernedOfTunbridgeWells May 30 '09 at 10:22
  • 2
    In C there is no exceptions like in Java, and using goto to implement something similar is the right thing to do. – hlovdal May 30 '09 at 21:57
6

My concerns going from OO back to C were addressed in David Hanson's "C Interfaces and Implementations".

C Interfaces and Implementations

Seriously, its approach made a huge difference in avoiding accidentally building the large ball of yarn that many non-oo systems wind up as.

navicore
  • 1,601
  • 2
  • 24
  • 43
  • 1
    +1 - thanks for reminding me. I have heard of this book but I've never read it. Interfaces/implementations is a key procedural methodology and is made explicit in Modula-2. Will point to this in my post as I think this may be the the closest thing to a 'C architecture' book I've ever heard of. Good thinking 99 ;-} – ConcernedOfTunbridgeWells Jun 01 '09 at 09:07
2

Here's some interesting responses from a different question regarding OO programming in C. I made a post about some C code I worked with which basically impelmented object orientation stopping just short by not including virtual methods.

If I were doing C coding, I would use this technique to define 'objects'.

I find keeping Design Patterns in mind is always helpful, and can be implemented in most languages.

Here's a nice PDF discussing object oriented C programming.

Community
  • 1
  • 1
Kieveli
  • 10,548
  • 6
  • 48
  • 77
  • 1
    If you want to do OO in C, you should really use C++. If you want to use C, you should write nice, clear procedural code. – Chris Lutz May 29 '09 at 12:47
  • 1
    "If you want to use C, you should write nice, clear procedural code." Would you be able to recommend any resources to learn how to do this? This is the kind of thing i'm looking for. – K M May 29 '09 at 13:31
  • There are cases where using C is required. Not all processors have C++ compilers - especially if you're moving into an embedded situation. When you're working with code to run on low-level hardware, it's still in your best interest to code it in C, and then have a C++ wrapper. – Kieveli May 29 '09 at 13:37
  • OOP in C mostly boils down to implementing objects as structures and methods as standalone functions with an explicit instance pointer parameter. This is similar to how other languages tackle OOP, so that you call METHOD(object, arg1, arg2 ...). Of course, using function pointers you could also implement the more common obj.method(arg1,arg2) approach. – none May 29 '09 at 20:15
  • 1
    If you skip using function pointers, and go with the method of passing in the object struct pointer, then you leave the code way more readable =) – Kieveli Jun 01 '09 at 11:42
2

minix by tanenbaum

Peter Miehle
  • 5,777
  • 2
  • 35
  • 53
1

Larger applications? C's strength is when you have to deal with low level things like device drivers, schedulers, and other OS flavored things.

You can make C that operates like OO, but that will utlimately feel like an exercise in re-inventing the wheel..

JustJeff
  • 11,830
  • 5
  • 43
  • 62
  • Those can be pretty large applications, I know for example the Linux Kernel is written in C, but i'm essentially asking for information on techniques on how you structure a C program once you get over like 500 lines. I know alot of people seem to have misread the question and think I want to use OO in C, but I'm actually interested in ALL techniques that can be used to structure larger C Programs. – K M May 31 '09 at 13:23
  • Oh ok - well, a common approach is to try to divide the app into modules and then assign each module to a .c file, 'advertise' its merits in header (.h) files, and then use #includes to resolve source dependencies. Works pretty well up to a few KLOC. Where things usually go all pear shaped is when the dependencies get out of control, and .h files start including other .h files, or multiple source directories become necessary, or the ultimate horror, somebody starts putting variable declarations in .h files.. – JustJeff May 31 '09 at 13:44
1

One minor thing is to order your .c files "backwards" - i.e. put main() at the bottom of the file, and always make sure local functions (ones that aren't in your .h file, that you just wrote for use in that file) live above where they're first used. This means you don't have to write prototypes for them, which is one less thing to fiddle with if you have to change their API.

Then, as a bonus, the 'gD' command in vim will always go to the definition of a function, if it's in the same file :)

C Pirate
  • 456
  • 3
  • 7
0

While it is written as a somewhat language-agnostic text, Code Complete provides a lot of good guidance on code structure and organization, along with construction practices.

Phil Miller
  • 32,214
  • 11
  • 62
  • 86