Is strongly-typed programming conceivable for C code using gcc ?

John Love-Jensen eljay@adobe.com
Thu Sep 20 15:05:00 GMT 2007


Hi Alexandre,

> Is that true?

I wouldn't do it that way, but that's one way to do it.

I'd do it this way:

Foo.h

  struct CW_C_MsgUp_s;
  typedef struct CW_C_MsgUp_s* CW_C_MsgUp_t;
  CW_C_MsgUp_t CW_C_MsgUp_new();
  void CW_C_MsgUp_delete(CW_C_MsgUp_t);

  struct CW_C_MsgDown_s;
  typedef struct CW_C_MsgDown_s* CW_C_MsgDown_t;
  CW_C_MsgDown_t CW_C_MsgDown_new();
  void CW_C_MsgDown_delete(CW_C_MsgDown_t);

private/FooImpl.h

  struct CW_C_MsgUp_s
  {
    int refCount;
    int value;
    int anotherValue;
    int somethingElse;
    double bigValue;
    char* someRelevantString; // Could be NULL.
  };

  struct CW_C_MsgDown_s
  {
    int refCount;
    double x;
    double y;
    double z;
    int active;
  };

private/Foo.c

  #include "Foo.h"
  #include "FooImpl.h"
  // The function implementations.

Where libFoo.a and Foo.h are shipped to customers.  And private/FooImpl.h
and private/Foo.c are not shipped to customers.

> Why did you talk about a "reference counting mechanism"?

When working with opaque types, having a reference count can make working
with the item much more convenient for the end-user.

You don't have to provide such a mechanism.

Having reference counts is not necessary; it's only useful in some domains.
Usually depending on how the items are used.

> Because in my implementation my C "constructor" and "destructor" functions do
not have any counting mechanism, is that a problem.

No, it's not necessarily a problem.  Depends on how the items are used.

> Actually, I rely on the users/callers of the functions to call the
"destructor" as many times as the "constructor", avoiding memory losses.

That's good.  That way your heap management may (or may not) be separate
from the heap management of the user side.  Having the possibility of it
being separate can be a very good thing, in some circumstances.

> But, if you talked about a "reference counting mechanism", is it to prevent
memory leaks?

A reference counting mechanism cannot prevent memory leaks.  A user could
still "construct" an item, and neglect to release it (hence, decrementing
its reference count, and destruct the item when the count hits zero).

A reference counting mechanism is a convenience for the users of your items.
Which, depending on the usage pattern, may-or-may-not be useful.

> By implementing a kind of basic "garbage collector", destroying undeleted
allocated structures? Is that what you meant?

No, a reference counting mechanism is not a basic garbage collector.  Those
are different technologies. (Although a basic garbage collector could use
reference counting itself as its own mechanism, which introduces certain
problems.)

The difference between the two is in a garbage collector, the user does not
have to worry about reference counts or releasing -- that's the garbage
collector's job.

> Yes and no. Because if it will or will not be confused with such a typedef.

If you use (note that the typedef is not a pointer)...

typedef struct CW_C_MsgUp_s CW_C_MsgUp_t;

...then you are pretty much allowing (requiring) that your structure be
visible, which means it is not opaque.  And beyond that, you are definitely
putting a burden on the users of your type to comply with your constraints
in their code, rather then putting your constraints in the type itself.

If you use (note that the typedef is a pointer)...

typedef struct CW_C_MsgUp_s* CW_C_MsgUp_t;

...then you are able to make your structure opaque (i.e., in your private
header file, as an implementation detail; one that can be changed when the
underlying implementation rev's).

> Because my "*_new" and "*_delete" functions will allocate and destroy objects
> (structures, classes, int, char buf, etc, who knows, that doesn't matter) and
> the allocation function on failure can return a null pointer. Therefore how
> will the caller test the returned value (not considered as pointer because of
> typedef) with null?

The typedef *IS* a pointer.

The caller can test the returned value with NULL.

  struct CW_C_MsgUp_s; // Forward declare.
  typedef struct CW_C_MsgUp_s* CW_C_MsgUp_t; // CW_C_MsgUp_t *IS* a pointer!

  CW_C_MsgUp_t my_up_msg = NULL;
  my_up_msg = CW_C_MsgUp_new();
  if(my_up_msg == NULL)
  {
    return -1;
  }

> I think a workaround could be to pass the "my_up_msg" or "my_down_msg"
> variables as parameter of "*_new" functions and test an integer returned value
> of "*_new", telling how everything goes?

I would not implement it that way.

> Which of example1 and example2 is better to use?

example1, in my opinion.

> On the same kind of reflexion, how do I have to initialize my_up_msg?
>   1. my_up_msg = 0;
>   2. my_up_msg = null;
>   3. memset(my_up_msg, 0, sizeof(my_up_msg))?
>   4. memset(&my_up_msg, 0, sizeof(my_up_msg))?

I would use a fifth way:

my_up_msg = NULL;

I would not introduce a lower-case identifier 'null'.

> The solution I feel is the good one are 2 and 4, but they mean that I do not
avoid the fact that "my_up_msg" is a typedef-ed pointer.

Correct.  You do not avoid the fact that "my_up_msg" is a typedef-ed
pointer.

You know it, your users know it.  You rely on it, your users rely on it.

The important bit is that my_up_msg is a TYPED pointer (hence, the
type-safety you are trying to obtain), and it also points to an OPAQUE
structure which is not provided in the public headers (hence, the users
cannot manipulate it or observe it behind your back, and you can change the
backing store structure without concerns about breaking user's code).

> How could I do to not confuse the users and me?

Documentation.

Perhaps adapt the Hungarian-ish convention of using '_p' or '_ptr' (meaning
"pointer"), or if you use reference counting '_r' or '_ref' (meaning
"reference"), instead of '_t' (meaning "type") for the identifier suffix.

Carbon tends to use the suffix 'Ref' (meaning "reference").  So they have
CFStringRef, CFAllocatorRef, CFArrayRef, et cetera.  The "CF" prefix in this
case stands for CoreFoundation, which is one of Apple's frameworks (Apple's
term for what I'd call a SDK).

Whatever the convention you adopt, I strongly recommend consistency.

> I'm aware that this solution is very interesting because it does not allow
people to dereference the variable.

That's the OPAQUE part, which is very nice to have in a SDK.

> But is that a recommended coding rule?

It is a particular coding paradigm.

Many years ago, before I worked with X11, it is one that I would have been
skeptical of beforehand.

Fortunately, with experience, it has proven itself to me to be useful, and
the Carbon way of using forward reference structures in the public header is
a lot more type-safe over the X11 style of using typedef'd void*.

> Thanks a lot for all your help.

You're welcome.

Sincerely,
--Eljay



More information about the Gcc-help mailing list