3

I am looking for a way to make private style typedefs that can only be accessed or manipulated from a specific set of function calls (setBit(bit_typ *const t), getBit(bit_typ *const t)). I am looking for a way to do this without using malloc, does anyone have any ideas?

EDIT:// this question is different than this one because it is looking for ways to get as close to a "private" structure whereas the other question (TL;DR is there a way to define an opaque type which can nonetheless be allocated on stack, and without breaking strict aliasing rule ?) looks for a solution to a problem related to one possible solution to my question.

Community
  • 1
  • 1
jonbovy
  • 127
  • 3
  • 11
  • 7
    What does `malloc` have to do with scoping of typedefs?! – ikegami Jul 06 '15 at 19:21
  • 5
    Only declare the `typedef` in the .c files that define the "specific set of function calls". – chux - Reinstate Monica Jul 06 '15 at 19:25
  • There are similar questions which have been answered using malloc in order to allocate memory, that solution will not work for me. – jonbovy Jul 06 '15 at 19:31
  • `Malloc` has absolutely nothing to do with privacy. In fact, C itself does not lend itself to making things "private", though you can try to make them opaque. – David Hoelzer Jul 06 '15 at 19:34
  • 1
    @Weather Vane: Yes, but the whole point is that `bit_typ` only needs to be available as "forward" declaration `typedef struct bit_typ bit_typ;` - a typical opaque typedef. Unfortunately, this prevents the user from defining objects of `bit_typ` type. The user has to call an API function (which knows everything about `bit_typ`), which will `malloc` the object. This is the `malloc` the OP is talking about and that's why it's often mentioned in connection with opaque types. `malloc` is the price we have to pay for the full opacity. There's no way around it, unless you go the way I describe below. – AnT stands with Russia Jul 06 '15 at 19:55
  • possible duplicate of [Opaque types allocatable on stack in C](http://stackoverflow.com/questions/31195551/opaque-types-allocatable-on-stack-in-c) – ecatmur Jul 06 '15 at 20:24

1 Answers1

5

One way to do it is to expose the total size of the opaque type and make used declare the objects of your opaque type as unsigned char [N] buffers. For example, let's say you have some type OpaqueType, internals of which you want to hide from the user.

In the header file (exposed to the user) you do this

typedef unsigned char OpaqueType[16];

where 16 is the exact byte-size of the type you want to hide. In the header file you write the whole interface in terms of that type, e.g.

void set_data(OpaqueType *dst, int data);

In the implementation file you declare the actual type

typedef struct OpaqueTypeImpl
{
  int data1;
  double data2;
} OpaqueTypeImpl;

and implement the functions as follows

void set_data(OpaqueType *dst, int data)
{
  OpaqueTypeImpl *actual_dst = (OpaqueTypeImpl *) dst;
  actual_dst->data1 = data;
}

You can also add a static assertion that will make sure that sizeof(OpaqueType) is the same as sizeof(OpaqueTypeImpl).

Of course, as it has been noted in the comments below, extra steps have to be taken to ensure the proper alignment of such objects, like _Alignas in C11 or some union-based technique in "classic" C.

That way you give the user opportunity to declare non-dynamic object of OpaqueType, i.e. you don't force the user to call your function that will malloc such objects internally. And at the same time you don't expose to user anything about the inner structure of your type (besides its total size and its alignment requirement).

Note also that OpaqueType declared in that way is an array, meaning that it is not copyable (unless you use memcpy). That might be a good thing, if you want to actively prevent unrestrained user-level copying. But if you want to enable copying, you can wrap the array into a struct.

This approach is not terribly elegant, but that's probably the only way to hide implementation when you want to keep objects of your type freely user-definable.

AnT stands with Russia
  • 312,472
  • 42
  • 525
  • 765
  • That *really* won't work; you can't alias with anything other than the `char` types. Use `alignas` (C11) or equivalent. – ecatmur Jul 06 '15 at 19:36
  • @ecatmur: Not true. In order to "alias" you's have to *access* the data through `OpaqueType` type. However, in this approach you never actually access it as `OpaqueType`. All accesses are supposed to be made through `OpaqueTypeImpl` after casting. That is not aliasing. – AnT stands with Russia Jul 06 '15 at 19:37
  • I'd be very wary of doing that; you're violating 6.5p6: "The *effective type* of an object for an access to its stored value is the declared type of the object, if any." Admittedly, using a `char` buffer suffers the same problem, but a compiler is far less likely to exploit the undefined behavior in that case. – ecatmur Jul 06 '15 at 20:23
  • @ecatmur: I don't see how can such strict approach to aliasing and UB coexist with the permission to access "non-active" members of a union, which was introduced into the language in TC3 for C99. – AnT stands with Russia Jul 06 '15 at 20:32
  • AIUI to access a non-active union member you have to do so via a lvalue of the union type, so the compiler knows to relax aliasing restrictions (alternatively: disable optimizations) for that access. – ecatmur Jul 06 '15 at 20:38
  • @ecatmur: Good point. It does seem to be that restrictive. This rises an interesting question: what if I want to implement my own highly localized allocator, which will manage custom memory blocks inside a statically declared `unsigned char storage[2048];` storage region. The array is not allocated, which means that it has a specific *effective type.* It appears that there's no way to implement my allocator legally in C, since in any case I'll eventually end up accessing object with effective type `unsigned char` as objects of some other type. – AnT stands with Russia Jul 06 '15 at 20:48
  • Yes, it does seem to make it impossible to create an allocator backed by a static buffer, which is why I would be very surprised if a compiler were to fully enforce the aliasing rules for a char buffer. There's also C++ compatibility to consider e.g. std::aligned_storage. – ecatmur Jul 06 '15 at 21:07