On 05/22/2012 05:18 AM, Noob wrote:
> Hello everyone,
>
> I'm implementing a wrapper around platform-dependent "primitives",
> and I'm trying to have 0 platform-dependent code (even includes)
> in the "module" interface header.
>
> For example, consider this possible interface for a semaphore
> (whatever that is):
IIRC, some standard other than the C standard (I think it's POSIX or one
it's descendent's) reserves all identifiers ending in _t for use as type
names associated with the standard libraries. You might want to change
the name of Sem_t to avoid conflicts.
> Sem_t *Sem_create(int val);
> void Sem_delete(Sem_t *sem);
> int Sem_signal(Sem_t *sem);
> int Sem_wait(Sem_t *sem);
> int Sem_wait_timeout(Sem_t *sem, unsigned wait_ms);
>
> I could use an opaque (void *) pointer to "hide" the details of
> a (Sem_t) but I do want the compiler to throw an error when an
> imprudent user calls, e.g.
>
> char *str = "booya";
> Sem_signal(str);
>
> One method I've seen a few times, is to "lie" to the user code
> (and, to some level, to the compiler) by pretending "yeah, this
> Sem_t is a struct, and I'll provide the definition sometime later"
> (I think it's called a tentative declaration). But I've been told
> that the compiler always gets its revenge when it's lied to.
There's an easy solution to that: don't lie. In the public header,
declare your struct type without a definition. In a private header
shared only by your implementation of those functions, define the same
exact struct type as containing the implementation-dependent features it
needs to contain, and define your functions as taking and/or returning
pointers to that struct type.
> Could you please confirm whether the following approach is safe,
> and does what I want?
>
> $ cat wrap_sem.h
> #ifndef TRUE_SEM_T_DEFINITION
> typedef struct dummy_Sem_t Sem_t;
> #endif
> Sem_t *Sem_create(int val);
> void Sem_delete(Sem_t *sem);
> int Sem_signal(Sem_t *sem);
> int Sem_wait(Sem_t *sem);
> int Sem_wait_timeout(Sem_t *sem, unsigned wait_ms);
>
> Users just include "wrap_sem.h" and they're not supposed to "peek"
> inside a Sem_t (in fact, they can't since Sem_t is an incomplete type).
>
> $ cat test.c
> #include "wrap_sem.h"
> int main(void)
> {
> Sem_t *toto = Sem_create(42);
> *toto;
> Sem_wait("foo");
> return 0;
> }
>
> $ gcc -Wall -Wextra -std=c89 test.c
> test.c: In function 'main':
> test.c:5:3: error: dereferencing pointer to incomplete type
For a proper opaque type, the user must never dereference the pointer.
This is not normally much of a problem, because the user seldom has need
to dereference it. The main exception is copying; if you need the user
to have some means of copying objects of the opaque type, you need to
provide a copying function. In the simplest case:
Sem_t *Sem_copy(const Sem_t *source)
{
Sem_t *dest = malloc(sizeof *dest);
if(dest)
*dest = *source;
return dest;
}
You'll need an object factory: a function that returns a pointer to an
newly allocated and appropriately initialized object of the opaque type.
You'll also need to let the users know that they should free() the
pointers when they're done with them.
In more complex cases, if Sem_t itself contains pointers, the pointed-at
objects might need to be copied too, and you might need to define a
special function for destroying such objects that free()s the pointed-at
memory.
> test.c:6:3: warning: passing argument 1 of 'Sem_wait' from incompatible pointer type
> wrap_sem.h:7:5: note: expected 'struct Sem_t *' but argument is of type 'char *'
>
> (Side issue: there seems to be a small QoI issue with the compiler's note,
> as there is no 'struct Sem_t' AFAIU, there is a 'struct dummy_Sem_t' and
> a 'Sem_t'. Do you agree? End digression)
>
> In the actual implementation file, I define TRUE_SEM_T_DEFINITION
> and then define the "real" Sem_t type.
Drop that. It should have the same definition in both places. Just
declare struct dummy_Sem_t as containing an object of the type that you
wanted to use for Sem_t.
> Is this an (the?) idiomatic way to hide implementation details?
No. The alternative I've described is the idiomatic way.
> Is there a chance that this could bite me?
Yes. Your approach has undefined behavior, because the declaration of
your functions that's visible to users is incompatible with the
definitions of those same functions.
> Could this confuse other tools, such as a debugger?
Quite likely.
> Any unforeseen consequences?
Almost certainly.
--
James Kuyper