1

I'm new to C programming and trying to write a simple example. Percisely I tried to abstract over a type implementation and simply use typedef and specify operations I can do with this type. I understand that at that point the type is incomplete, but I was intended to complete it into c-file, not header. Here is it:

test.h

#ifndef _TEST_H
#define _TEST_H

typedef my_type_t;

void init(my_type_t **t);

#endif //_TEST_H

test.c

#include <stdlib.h>
#include "test.h"
                  //      implementation details
struct my_type_t{ //<---- completening my_type_t to be a struct with 1 field
    int field;
};

void init(struct my_type_t **t){ //<--- error: conflicting type for init
    *t = malloc(sizeof(struct my_type_t));
    (*t) -> field = 42;
}

Is something like this possible? I wanted the implementation completely hide all the details about the actual type definition exposing only operations that can be done with it.

UPD: If we rewrite the c-file as follows:

#include <stdlib.h>
#include "test.h"

struct internal_my_type_definition_t{
    int field;
};

void init(my_type_t **t){
    struct internal_my_type_definition_t *st = malloc(sizeof(struct internal_my_type_definition_t));
    st -> field = 42;
    *t = st;
}

Is there any problem with such an implementation?

St.Antario
  • 26,175
  • 41
  • 130
  • 318
  • 2
    `malloc(sizeof(struct my_type_t))` would be better written as `malloc(sizeof **t)` - The sizeof operator can be applied to expressions, and this way it evaluates to the size of the object, no matter how you name it (or rename it). This will avoid subtle bugs. – StoryTeller - Unslander Monica Nov 26 '18 at 05:59
  • @StoryTeller Interesting note, thanks. But is it common to do something like the following: https://pastebin.com/DpjMY4cm . It compiles fine, but allows to abstracting away from the `struct` in header. – St.Antario Nov 26 '18 at 06:04
  • I'm sorry but I can't access pastebin links – StoryTeller - Unslander Monica Nov 26 '18 at 06:08
  • @StoryTeller Added the example as an update. – St.Antario Nov 26 '18 at 06:13
  • 1
    There isn't really a *problem* with it, but as I mentioned in my answer, the more idiomatic API would be to return the `struct my_type_t*` instead. – nemequ Nov 26 '18 at 06:15
  • 1
    All structure pointer types are compatible (don't recall if it requires a cast), so no immediate problem. But this gains you nothing. What are you "hiding", a tag name? – StoryTeller - Unslander Monica Nov 26 '18 at 06:18
  • 2
    Note that you should not, in general, create function, variable, tag or macro names that start with an underscore. Part of [C11 §7.1.3 Reserved identifiers](https://port70.net/~nsz/c/c11/n1570.html#7.1.3) says: — _All identifiers that begin with an underscore and either an uppercase letter or another underscore are always reserved for any use._ — _All identifiers that begin with an underscore are always reserved for use as identifiers with file scope in both the ordinary and tag name spaces._ See also [What does double underscore (`__const`) mean in C?](https://stackoverflow.com/a/1449301) – Jonathan Leffler Nov 26 '18 at 06:26
  • @StoryTeller I thought that being `struct` or `union` is implementation details. So I tried to hide them... In particular it would be counter-intuitive to declare `struct` in a header file and later on change the implementation to `union`. – St.Antario Nov 26 '18 at 06:56
  • 1
    @St.Antario - It would also be an ill-formed program if you do. But honestly I can't easily envision a case where one would deploy a library and then decide they needed a union to begin with. – StoryTeller - Unslander Monica Nov 26 '18 at 06:59
  • Oh and regarding the original question you asked me about your edit. That idiom I mentioned still applies, it's just that the pointer with the type information is `st`, so you'd use `sizeof *st`. – StoryTeller - Unslander Monica Nov 26 '18 at 07:08
  • 1
    `typedef my_type_t;` -> `typedef struct my_type_t my_type_t;` – Lundin Nov 26 '18 at 09:04

2 Answers2

5

In your header, change

typedef my_type_t;

to

struct my_type_t;

It's a pretty common pattern. Just keep in mind that you'll need a function to allocate the struct on the heap and free it; one of the pieces of information you're hiding is the size of the struct, so the API consumer can really only deal with pointers to the struct not the struct itself.

The idiomatic API would be something like

struct my_type_t* my_type_new(void);
void my_type_free(struct my_type_t* self);

my_type_init would typically be used to initialize an already allocated instance, which is really only useful if you want to chain up to it in the *_new function of a subtype.

Edit: in response to your follow-up question, you could conceivably do something like this in your header:

#if !defined(MY_TYPE_NS)
#  define MY_TYPE_NS struct
#endif

typedef MY_TYPE_NS my_type_t my_type;

my_type* my_type_new(void);

/* ... */

Then, in your *.c file:

#define MY_TYPE_NS union
#include "test.h"

union my_type_t {
  /* ... */
};

my_type* my_type_new(void*) {
  my_type* res = malloc(sizeof(my_type));
  res->field = 42;
  return res;
}

Which I find to be only slightly evil. I'd probably just use a union nested inside of the struct to avoid any surprises in the code.

nemequ
  • 16,623
  • 1
  • 43
  • 62
  • or `typedef struct my_type_s my_type_t` if he wants to use it as `my_type_t` – Yakup Türkan Nov 26 '18 at 05:57
  • @YakupTürkan I wanted actually to abstract over if it was a `struct` or `union`, but this seems not possible. – St.Antario Nov 26 '18 at 05:58
  • No, in that case you'd probably just make a struct with a single field and have the type of the field be a union. It can be an anonymous union, but that's a GNU C extension. – nemequ Nov 26 '18 at 06:00
  • @nemequ - Anonymous unions have been standard for the past 19 years. – StoryTeller - Unslander Monica Nov 26 '18 at 06:01
  • @St.Antario you cannot hide that detail from header, as far as I know, the headers are used in type inference. – Yakup Türkan Nov 26 '18 at 06:01
  • @StoryTeller: [citation needed]. Some quick googling makes me think they're available in C11, but not C99… – nemequ Nov 26 '18 at 06:04
  • @nemequ - You are correct, C11, so only 7 years. I drift between C and C++ so sometimes I get my dates mixed up. – StoryTeller - Unslander Monica Nov 26 '18 at 06:06
  • Unfortunately, MSVC doesn't even fully support C99 yet, much less C11, so I'd still generally hold off on using an anonymous union. It's a pretty minor feature to drop support for MSVC over, and unfortunately a lot of people are *really* firmly stuck with it for now. – nemequ Nov 26 '18 at 06:10
  • Understood this pattern, thanks. I added some update and very interesting to know if it is common to do so? Is there some problem with the implementation? I think by doing so we could abstract away of the "kind" (`struct`, `union`) of the `my_type_t`. – St.Antario Nov 26 '18 at 06:13
  • MSVC does support it (https://godbolt.org/z/GYCxby). First as an extension from its C++ end, no doubt, and now with many C99 features being made optional, it supports it "almost" as if it were standard conforming compiler. – StoryTeller - Unslander Monica Nov 26 '18 at 06:16
  • Not by doing it this way. There are a couple of hacks you could use to abstract away whether something is a struct or union, like just creating a typedef to void* and using that in you API (please don't, you lose type safety), or abusing the preprocessor to possibly change your `struct my_type_t` to `union my_type_t` in your *.c file only. I'll add a quick explanation to my answer… – nemequ Nov 26 '18 at 06:19
  • @nemequ That's not right. It even supports an extended variant using aliases. And `void *` is something completely different. – too honest for this site Nov 26 '18 at 06:36
  • What's not right? `void*` is different from what I suggested, but it would work. You could pass in something that was mapped to a `struct my_type_t*` and have the function body deal with it as a `union my_type_t*` (or whatever other pointer type you want). – nemequ Nov 26 '18 at 06:40
1

The design pattern you are looking for is called "opaque type"/"opaque pointers".

You almost have it correctly, you just need to specify the type explicitly in the header:

typedef struct my_type_t my_type_t;

This is both a typedef and a forward declaration of an incomplete type, which is completed in your .c file and not visible to the caller.

Now the caller can declare pointers to this type, but not objects. They can't access struct members - we've achieved private encapsulation. You have to design your functions to always take a pointer type.

Lundin
  • 195,001
  • 40
  • 254
  • 396