Stuart Redmann said:
ROFL. I like this statement.
Global variables are quite evil, but I think it's far worse when the
programming language does not support any kind of information hiding
(like C in comparison to C++). So you never know which part of the
"interface" is meant to be used by you and which is just an internal
function. Whenever I look at the LINUX kernel I just wanna scream
(actually, that goes for _any_ C code, but, strangly, also for boost).
errm, my usual practices (for C) go like this:
I typically avoid globals, instead, most stateful data is kept in "contexts"
which are passed around first class;
usually naming conventions are used to help separate public API calls from
internal calls.
using C does not itself mean using globals all over the damn place, just one
has to manage things a little more manually. since there are not classes,
one is instead left using structs and function pointers for many of the same
purposes.
so, usually if a call looks like:
fooSomethingOrAnother();
it is meant as an API call, but if it looks like:
FOO_SomethingOrAnother();
it is usually meant as internal to a library (but may sometimes be used
externally with caution, as it is subject to change or be removed).
FOO_Bar_SomethingOrAnother();
usually means it is specific to a particular component, and code outside the
FOO library shouldn't even look at this.
foo_bar_somethingoranother();
usually is used for internal functions (usually marked static, otherwise
still internal).
similar is often used for typenames, where:
fooTypeName
usually means a public type.
however:
FOO_TypeName
means the type is internal.
often, I will also leave structs incomplete externally (headers included
from outside the library) so that external code is not so tempted to try to
dig around in structure internals (I typically prefer to keep nearly all
structure internals hidden, but sometimes may expose "vtable" or "interface"
structs, whos main purpose is to allow passing an API around without needing
to physically link against the exporting library).
conventions haven't always been kept entirely consistent, but this is the
general policy I follow.
I also use C++, but my policy is not to use C++ across API borders (APIs are
generally constrained to being C friendly, and also because C++ and DLL
mechanics are not always exactly friendly...).
also, many libraries are pure C, as I tend to use some code-processing tools
which are not currently able to parse C++...
example of how some of this can work:
typedef struct ...
struct foo_obj_vtable_s
{
void (*someMethodA)(fooObj *self);
void (*someMethodB)(fooObj *self);
};
struct foo_obj_s
{
foo_obj_vtable *vtable;
//fields...
};
....
C source:
foo_obj_vtable foo_obj_vt =
{
foo_obj_someMethodA,
foo_obj_someMethodB
}
....
fooObj *fooNewFooObj()
{
fooObj *tmp;
//via malloc:
tmp=(fooObj *)malloc(sizeof(fooObj));
memset(tmp, 0, sizeof(fooObj));
//via GC:
tmp=(fooObj *)gctalloc("foo_obj_t", sizeof(fooObj));
//...
tmp->vtable=&foo_obj_vt;
return(tmp);
}
void fooSomeMethodA(fooObj *self)
{ self->vtable->someMethodA(self); }
....
inheritence gets to be a more complex matter, so traditional inheritence
(via physical extension) is not used as often.
more commonly, the base struct and vtable will provide all of the commonly
used fields and methods, and daisy-chaining is used to extend structs and
vtables (so, each extendable struct will usually provide space to supply a
pointer to point to more data for a given object, although they may be
allocated as a single larger heap object).
the above strategy is more commonly used in my case, but competes some
against the use of API-call-based object systems.
or such...