I have no professionnal experience on C (only on C++), so don't take my advices, tricks and tips too seriously, as they are "object-like-oriented".
Almost Object C?
Simulating basic object-like features can be done easily:
In the header, forward declare your type, typedef it, and declare the "methods". For example:
/* MyString.h */
#include <string.h>
/* Forward declaration */
struct StructMyString ;
/* Typedef of forward-declaration (note: Not possible in C++) */
typedef struct StructMyString MyString ;
MyString * MyString_new() ;
MyString * MyString_create(const char * p_pString) ;
void MyString_delete(MyString * p_pThis) ;
size_t MyString_length(const MyString * p_pThis) ;
MyString * MyString_copy(MyString * p_pThis, const MyString * p_pSource) ;
MyString * MyString_concat(MyString * p_pThis, const MyString * p_pSource) ;
const char * MyString_get_c_string(const MyString * p_pThis) ;
MyString * MyString_copy_c_string(MyString * p_pThis, const char * p_pSource) ;
MyString * MyString_concat_c_string(MyString * p_pThis, const char * p_pSource) ;
You'll see each functions is prefixed. I choose the name of the "struct" to make sure there won't be collision with another code.
You'll see, too, that I used "p_pThis" to keep with the OO-like idea.
In the source file, define your type, and define the functions:
/* MyString.c */
#include "MyString.h"
#include <string.h>
#include <stdlib.h>
struct StructMyString
{
char * m_pString ;
size_t m_iSize ;
} ;
MyString * MyString_new()
{
MyString * pMyString = malloc(sizeof(MyString)) ;
pMyString->m_iSize = 0 ;
pMyString->m_pString = malloc((pMyString->m_iSize + 1) * sizeof(char)) ;
pMyString->m_pString[0] = 0 ;
return pMyString ;
}
/* etc. */
If you want "private" functions (or private global variables), declare them static in the C source. This way, they won't be visible outside:
static void doSomethingPrivate()
{
/* etc. */
}
static int g_iMyPrivateCounter = 0 ;
If you want inheritance, then you're almost screwed. If you believed everything in C was global, including variable, then you should get more experience in C before even trying to think how inheritance could be simulated.
Misc. Tips
Avoid multiple code-paths.
For example, multiple returns is risky. For example:
void doSomething(int i)
{
void * p = malloc(25) ;
if(i > 0)
{
/* this will leak memory ! */
return ;
}
free(p) ;
}
Avoid non-const globals
This include "static" variables (which are not static functions).
Global non-const variables are almost always a bad idea (i.e. see C API strtok for an example of crappy function), and if producing multithread safe code, they are a pain to handle.
Avoid name collision
Choose a "namespace" for your functions, and for your defines. This could be:
#define GROOVY_LIB_x_MY_CONST_INT 42
void GoovyLib_dosomething() ;
Beware defines
Defines can't be avoided in C, but they can have side effects!
#define MAX(a, b) (a > b) ? (a) : (b)
void doSomething()
{
int i = 0, j = 1, k ;
k = MAX(i, j) ; /* now, k == 1, i == 0 and j == 1 */
k = MAX(i, j++) ; /* now, k == 2, i == 0 and j == 3, NOT 2, and NOT 1 !!! */
}
Initialize your variables
Avoid declaring variables without initializing them:
int i = 42 ; /* now i = 42 */
int j ; /* now j can have any value */
double k ; /* now f can have any value, including invalid ones ! */
Uninitialized variables are causes of painful bugs.
Know all the C API
The C API function list as described in the K&R is quite small. You'll read the whole list in 20 minutes. You must know those functions.
Wanna some experience?
Rewrite the C API. For example, try to write your own version of the string.h functions, to see how it is done.