0

I have a module whose implementation I want to hide from its clients.

I chose to declare an opaque type which is actually a pointer to structure to be defined only in the implementation.

It all is working fine, except that I can assign the zero value to a variable of this type, which I want to avoid.

Here's an example in C.

header file foo.h

/* foo.h */
typedef struct foo *foo_t; /* <- sorry this was obviously flawed, the '*' was missing */


extern void foo_create( foo_t *t );
extern void foo_destroy( foo_t *t );
extern void foo_tile( foo_t x );

implementation file foo.c

/* foo.c */

#include <stdlib.h>

#include "foo.h"

struct foo {
    int some_member;
};

void foo_create( foo_t *t )
{
    if ( *t==0 ) {
        *t = malloc( sizeof(struct foo) );         
    }
}

void foo_destroy( foo_t *t )
{
    if ( *t!=0 ) {
        free(*t);
        *t    = 0;
    }
}


void foo_tile( foo_t t )
{
    t->some_member++;
}

And now here is an example client that uses the module: bar.c:

#include "foo.h"

int main( int argc , char **argv )
{
    foo_t toe;

    foo_create( &toe );
    toe    = 0;  /* <-- How to make the compiler (gcc) refuse this? */
    toe    = 1;  /* <--- the compiler rejects this YAY!!            */
}

The opaque type is actually a pointer to a dynamically allocated structure; If I assign the value 0 to it I incur a memory leak which could be avoided If the compiler denied the assignment of 0 to this opaque pointer.

Assigning non-null values to the pointer isn't accepted by the compiler, so I supose that with a little more effort the same could be achieved for zero value.

Is it possible to disable this assignment? How can I achieve that? If using some bit of C++ or gcc specific constructs is needed, I'll be ok with that, although a pure C solution would be nice.

Thanks in advance.

amso
  • 514
  • 1
  • 6
  • 14
  • 1
    C++ or C? They're not the same, you probably should pick just one. – Seth Carnegie Aug 10 '11 at 18:42
  • Also 0 is the only integer that can be implicitly converted to any pointer type. You can't stop that if `foo_t` really is a pointer. It's like trying to stop `5` from being assigned to an `int`. – Seth Carnegie Aug 10 '11 at 18:43
  • the huge difference between 0 and 1 is that 0 in this case is 'the null pointer' which is a perfectly valid value for any pointer. Also, since this is tagged C++, why don't use new and delete instead of free and malloc? Then you can take advantage of constructors and destructors. – stijn Aug 10 '11 at 18:43
  • 1
    And no amount of effort can change that. – Seth Carnegie Aug 10 '11 at 18:44
  • 1
    Also I doubt this is really how the code looks, because you `typedef struct foo foo_t;` but then `foo_t toe;` and `toe = 0;`. That wouldn't be possible because you can't assign `0` to a struct. – Seth Carnegie Aug 10 '11 at 18:48
  • @Seth Carnegie I'm pretty sure it's not the real code. – cnicutar Aug 10 '11 at 18:55
  • This is obviously not c++ code, as that ends up in several compiler errors. Not just for the implicit conversion from to void*, but also because `struct foo x = 0;` is NOT valid c++ code as far as I see (that does work in c??) – Voo Aug 10 '11 at 18:57
  • @Voo refer to my previous comment. – Seth Carnegie Aug 10 '11 at 19:05
  • 1
    You could use an array type to make your type non-assignable (since arrays aren't lvalues in C), but in order to do that you'll have to provide the full structure definition, which defeats the purpose of having an opaque type. – Adam Rosenfield Aug 10 '11 at 19:30
  • @Seth Carnegie : That's correct, I missed the '*' in the typedef :-(. Unfortunately it was in a critical place that changes the meaning of the whole example code. Sorry about the confusion. – amso Aug 11 '11 at 08:39

3 Answers3

4

First of all, your typedef is wrong: typedef struct foo foo_t; (and so it your main, otherwise the compiler will catch the assignments to structures).

For opaque types it is customary to do something like: typedef struct foo *foo_t;. Otherwise your toe wouldn't be a pointer in the example you posted (that's why you had to pass it with &). Considering the malloc in foo_create, I am pretty sure you typed the wrong typedef.

Second, ask yourself, how are you going to free memory ? By using a cleanup function (foo_destroy), right ? And the user is supposed to pass this pointer to the cleanup function.

So consider this: if the user is clueless enough to assign an integer to it, why wouldn't she be clueless enough to forget to cleanup ?

EDIT

Stéphane Gimenez commented typedef struct foo foo_t is what the OP want. I would like to underline that:

The only thing that the client can do with an object of such a type is to take its address, to produce an opaque pointer.

Community
  • 1
  • 1
cnicutar
  • 178,505
  • 25
  • 365
  • 392
  • You don't understand what is an opaque variable. `typedef struct foo foo_t` **is** what the OP want, preventing assignments too! – Stéphane Gimenez Aug 10 '11 at 19:08
  • Btw, I already wondered about this myself. I came to the conclusion that C was not smart enough to let you define abstract types like in ML and other languages. – Stéphane Gimenez Aug 10 '11 at 19:10
  • @Stéphane Gimenez Thank you for commenting :-) Keep this in mind: he tagged **"opaque-pointers"** and he used the term "pointer" in his question. – cnicutar Aug 10 '11 at 19:10
  • Ok :-) Well the tag is still kind of "related". In his example, the actual content is a pointer, but the point is to hide this to the user. – Stéphane Gimenez Aug 10 '11 at 19:25
  • About the edit: of course the **other thing** you can do with them is to pass them as arguments to functions defined in foo.cpp (which have access to the full data type). – Stéphane Gimenez Aug 10 '11 at 19:34
  • Sorry about the confusion. What I actually want is `typedef struct foo *foo_t`. – amso Aug 11 '11 at 08:27
1

I'm not sure you can do it that way. The compiler would fail in main():

    toe    = 0;  /* <-- How to make the compiler (gcc) refuse this? */

It would also fail in foo_destroy():

    void foo_destroy( foo_t *t )
    {
        if ( *t!=0 ) {
            free(*t);
            *t    = 0;  /* compiler refuses this also */
        }
    }

You might try returning the allocated memory from foo_create() directly rather than passing in the foo_t parameter (emulating a constructor):

extern foo_t * foo_create( void );

foo_t * foo_create( void )
{
    foo_t * t;

    t = malloc( sizeof(struct foo) );  

    return(t);       
}

int main( int argc , char **argv )
{
    foo_t * toe;

    toe = foo_create();

    toe = 0; /* Clearly a memory leak via reassignment */
    ...
}
JayG
  • 4,339
  • 3
  • 23
  • 19
  • NOTE: What I actually meant in the OP was `typedef struct foo *foo_t`. You made a valid point: If I can disable assignment `toe = 0;` quite probably I won't be able to perform `*t = 0;` in the "destructor". I guess I'll have to live with that. Even If I modify the interface to match your suggestion, I still have the same "problem", but at least, the programmer will notice leaks more clearly because what he sees is an explicit pointer. – amso Aug 11 '11 at 08:30
0

You're thinking about this wrongly. How would you initiate a local foo_t variable? If you type

void bar(void)
{
  foo_t var;
}

then var will contain garbage. The only way to make it clean is to type

void bar(void)
{
  foo_t var = NULL;
}

or 0 if you wish, but that should emit a warning. The code in your question is thereby not safe, it might crash.

What you can do is to add the nonnull attribute to foo_tile, i.e.:

void foo_tile( foo_t t ) __attribute__((nonnull(1)));

That will prevent foo_tile(NULL); and in some compilers even

foo_t var = NULL;
foo_tile(var);

though it'll likely just make it a warning, not a hard error.

Per Johansson
  • 6,697
  • 27
  • 34