Peter A. Darnell and Philip E. Margolis
C - A Software Engineering Approach (3rd ed.)
C Interfaces and Implementations: Techniques for Creating Reusable Software (!st ed.)
The first is clearly written and focuses on ANSI C (lexis, syntax and semantics), with a healthy dose of software engineering education along the way, which you may already have. (It does not yet cover the latest ISO changes.)
The second book is a true gem that teaches even seasoned programmers how to implement correct, clean and portable components as libraries in ISO C, with plenty of source code to show how the true masters of the language craft the finest code you can in C.
Most books how fragments of linked list implementations, but only this book shows complete a whole range of important ADTs you cannot live without (e.g. List, Ring, Atom) in C portably, with strong typing, bullet-proof error handling etc. (Hanson is also the author of lcc, a portable C compiler.)
I'm actually surprised that these are both are so little known, and that the second one hasn't seen more editions.
Another excellent reference is the "The Linux Programming Interface" book by Michael Kerrisk that documents most of the API available under Linux .
As for your question on control flow, I would have a function that parse's the response from the server and use's switch() to handle the various types of data being sent to your browser. There is a field in the http header which explains the content type and that way your browser should be able to call different functions based of what the content type is.
Also take a look at function pointers, both here Polymorphism (in C) and here How do function pointers in C work? . Function pointers would/could be a more eloquent way to solve your problem instead having large switch statements through out your code. With function pointers you can have one function that when called in your program behaves differently.
I will try to explain below with a browser as an example.
So lets say your browser just got back a http response from some server. The http response looks something like this in C.
struct http_header *header;
struct http_body *body
int (*decode_body)(char **);
static int decode_html(char **body)
/* Whatever it takes to parse html from http. */
static int decode_css(char **body)
/* Whatever it takes to parse css from http. */
int parse_http_header(struct http_res *http)
/* ... lots of other code to figure out content type. ... */
http->decode_body = &decode_html;
http->decode_body = &decode_css;
printf("Error can't parse body type.\n");
Now when we pass the http request to another part of the browser that function can call decode_body() in the http response object and it will end up with a decoded body it can understand, with out knowing what it's decoding.
int next_function(struct http_res * res)
/* Now we can decode the http body with out knowing anything about
it. We just call decode_body() and end up with a buffer with the
decoded data in it. */
rtrn = res->decode_body(&decoded_body);
if(rtrn < 0)
printf("Can't decode body.\n");
And finally nothing about C, perl or python means you will have to recode your program logic. You will have to design your program so that each module does not know about each other. The module knows about the interface and if you connect two modules that both "speak" the same interface you will have created a modular system. It's just like how the internet works the various computers on the internet do not need to know what the other computer is, or what it's doing, or it's operating system, all they need to know is TCP/IP and they can communicate with all the other devices on the internet.
Answering your first question, I recommend to encapsulate your structures using opaque pointers (a.k.a Handles).
For example, you may declare a handle to a linked list (here MS-like naming):
typedef struct linked_list_t* HLINKEDLIST;
We assume that linked_list_t is a generic one (composed of void pointers).
This way you can hide what a "handle" to a linked list is, or in what form is implemented (information hiding):
LinkedListCopy(HLINKEDLIST dst, const HLINKEDLIST src);
Handle subtypes are also commonly defined, such as PHLINKEDLIST (pointer to a linked list handle).
Related types can also be defined for convenience (and to use the limited information hiding available in C). e.g: the linked list element type can be defined as
typedef void* LLELEMENT;
There are good books on Data Structures in C to check. This is good: http://www.amazon.com/Interfaces-Implementations-Techniques-Creating-Reusable/dp/0201498413
Also note that LLELEMENT is actually compatible with void* so if you are defining other type def as:
typedef void* SYSTEMDATA;
SYSTEMDATA is compatible with LLELEMENT, so the compiler won't complain on:
int QuerySystemData(SYSTEMDATA* sd);
where lle is of type LLELEMENT.
This type checking can be enforced encapsulating simple members in structs. If I don't remember bad, declaring STRICT in a program using windows.h causes handles to be type-safer (incompatible between). Definitions like the following are common:
typedef struct __HWND
typedef __HWND* HWND;
If the simpler definitions were:
typedef int HWND;
typedef int HBITMAP;
The two handles would be type compatible and interchangeable on functions expecting to work with windows and functions expecting to work with bitmaps (potential horrible bugs).
The ability to create abstractions is pretty much a part of any programming language, including assembler. They're a property of the structure of the code, not of specific language features. For example, in the Linux kernel, which is written in C, there's an abstraction called a file descriptor that represents a stream of bytes that your program can read/write, whether it comes from a file on a disk, a serial port, a pipe, etc.
Interfaces also exist in all languages: they're just a well-defined set of calls that one piece of code uses to communicate with another piece of code. For example, the C runtime library (or any library) has an interface (API), and callers of the library don't need to know about the internal structure of the library to be able to call its services. (There's a pretty good book called "C Interfaces and Implementations" that talks about how to write reusable code in C.)
Classes are also possible to implement in C. A class is pretty much a data structure with a bunch of functions (the methods) that manipulate its data. In C, this can be represented by a structure and bunch of function pointers. The "constructor" function calls malloc() to create an instance of the class, sets its member variables from its parameters and returns a pointer to it. (Implementing inheritance is a bit tricky.)
To be able effectively split development tasks across a team, the same rules apply as in any other language: implement functionality behind well-defined APIs so that one part of the code can be used by another without having to know the details of how it works.