3

I have a little data-hiding module that looks like this:

/** mydata.h */
struct _mystruct_t;
typedef struct _mystruct_t mystruct;

mystruct *newMystruct();
void freeMystruct( mystruct** p );

/** mydata.c */
#include "mydata.h"
struct _mystruct_t {
    int64_t data1;
    int16_t data2;
    int16_t data3;
};
// ... related definitions ... //

For the most part, this is what I want; although simple, the struct has strict consistency requirements and I really don't want to provide access to the data members.

The problem is that in client code I would like to include the struct in another struct which I would like to allocate on the stack. Right now I am jumping through hoops to free the mystruct*s in some client code. Since a) mystruct is pretty small and I really don't think it's going to get big anytime soon and b) it's not a problem that client code has to recompile if I ever change mystruct, I would like to make the size of mystruct public (i.e. in the header).

Two possibilities I've considered:

/** mydata.h */
typedef struct {
    // SERIOUSLY DON'T ACCESS THESE MEMBERS
    int64_t data1;
    int16_t data2;
    int16_t data3;
} mystruct;

I think the drawbacks here speak for themselves.

OR

/** mydata.h */
#define SIZEOF_MYSTRUCT (sizeof(int64_t)+sizeof(int16_t)+sizeof(int16_t))
// everything else same as before...

/** mydata.c */
// same as before...
_Static_assert (SIZEOF_MYSTRUCT == sizeof(mystruct), "SIZEOF_MYSTRUCT is incorrect")

Of course this seems non-ideal since I have to update this value manually and I don't know if/how alignment of the struct could actually cause this to be incorrect (I thought of the static assert while writing this question, it partially addresses this concern).

Is one of these preferred? Or even better, is there some clever trick to provide the actual struct definition in the header while later somehow hiding the ability to access the members?

Luis
  • 1,210
  • 2
  • 11
  • 24
  • 1
    Don't use names starting with an underscore. Most such names are reserved for the implementation by the C standard. There are nuances in the defining verbiage (section 7.1.3 Reserved Names), but the simple rule 'do not use leading underscores' will steer you clear of trouble. – Jonathan Leffler Oct 03 '14 at 15:24
  • I thought it was just underscore followed by an uppercase? – Luis Oct 03 '14 at 15:25
  • The short answer is "you should not embed the structure in another"; you might embed a pointer to the structure in the other. – Jonathan Leffler Oct 03 '14 at 15:25
  • 1
    You thought wrong. One of the two bullet points says that: _— All identifiers that begin with an underscore and either an uppercase letter or another underscore are always reserved for any use._ so you are correct that underscore-capital is reserved. However, the next bullet is: _— 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._ – Jonathan Leffler Oct 03 '14 at 15:27
  • oops, thanks for the correction, now to go back to code I've previously written with leading underscores... :P – Luis Oct 03 '14 at 15:37

3 Answers3

1

You can create different .h file distributed to the end user that would define your secret structure just as byte array (you can't hide data without crypto/checksumming more than just saying "here are some bytes"):

typedef struct {
    unsigned char data[12];
} your_struct;

You just have to make sure that both structures are the same for all the compilers and options, thus using __declspec(align()) (for VC) in your library code, so for example:

// Client side
__declspec(align(32)) typedef struct {
    int64_t data1;
    int16_t data2;
    int16_t data3;
} mystruct;

To prevent structure from being 16B long instead of commonly expected 12B. Or just use /Zp compiler option.

Vyktor
  • 20,559
  • 6
  • 64
  • 96
1

I would stay with a configure time generated #define describing the size of the mystruct and possibly a typedef char[SIZEOF_MYSTRUCT] opaque_mystruct to simplify creation of placeholders for mystruct.

Likely the idea of configure time actions deserves some explanations. The general idea is to

  1. place the definition of the mystruct into a private, non-exported but nevertheless distributed header,
  2. create a small test application being built and executed before the library. The test application would #include the private header, and print actual sizeof (mystruct) for a given compiler and compile options
  3. create an appropriate script which would create a library config.h with #define SIZEOF_MYSTRUCT <calculated_number> and possibly definition of opaque_mystruct.

It's convenient to automate these steps with a decent build system, for examplecmake, gnu autotools or any other with support of configure stage. Actually all mentioned systems have built-in facilities which simplify the whole task to invocation of few predefined macros.

user3159253
  • 16,836
  • 3
  • 30
  • 56
-1

I've been researching and thinking and took one of my potential answers and took it to the next level; I think it addresses all of my concerns. Please critique.

/** in mydata.h */
typedef const struct { const char data[12]; } mystruct;
mystruct createMystruct();
int16_t exampleMystructGetter( mystruct *p );
// other func decls operating on mystruct ...


/** in mydata.c */
typedef union {
    mystruct public_block;
    struct mystruct_data_s {
        int64_t d1;
        int16_t d2
        int16_t d3;
    } data;
} mystruct_data;

// Optionally use '==' instead of '<=' to force minimal space usage
_Static_assert (sizeof(struct mystruct_data_s) <= sizeof(mystruct), "mystruct not big enough");

mystruct createMystruct(){
    static mystruct_data mystruct_blank = { .data = { .d1 = 1, .d2 = 2, .d3 = 3 } };
    return mystruct_blank.public_block;
}

int16_t exampleMystructGetter(mystruct *p) {
    mystruct_data *a = (mystruct_data*)p;
    return a->data.d2;
}

Under gcc 4.7.3 this compiles without warnings. A simple test program to create and access via the getter also compiles and works as expected.

Luis
  • 1,210
  • 2
  • 11
  • 24
  • The two other answers were good so I've upvoted but unless someone comes in with a good critique I will accept my answer as I think it's the simplest and it addresses all of my concerns. – Luis Oct 03 '14 at 19:35
  • You could cast `p` directly to `struct mystruct_data_s` – M.M Oct 03 '14 at 20:39
  • This code has an issue that `mystruct` might not be correctly aligned for `struct mystruct_data_s`. You could use C11 alignment specifiers to fix this, and/or update your `_Static_assert` to check that both have the same alignment requirement. – M.M Oct 03 '14 at 20:42
  • Alignment is the keypoint. Generally w/o additional assumptions about compiler and/or target platform you can't be sure about what alignment is proper for your struct. Even you make it byte-, word- or dword- aligned and use portsble (e.g. c++11-specific) alignment options you can't be 100%-sure that a chosen alignment will be correct for a given hardware platform and optimal (in size, performance etc) for the platform and _application_: some _applications_ may need optimization by size, other by speed etc, and things like these rarely should be decided on library level. – user3159253 Oct 03 '14 at 20:49
  • That's why I proposed the solution with configure-time config.h and "on target" configurations – user3159253 Oct 03 '14 at 20:51
  • If I make the `_Static_assert` check that `sizeof(mystruct) == sizeof(mystruct_data)` would that suffice? It would then seem to guarantee that they will always refer to the same block of memory and alignment with regards to mystruct_data_s is taken care of by handling the union. – Luis Oct 03 '14 at 22:27
  • I believe your last `return` statement is, strictly speaking, a violation of strict aliasing, a `mystruct` object is accessed through an lvalue of type `mystruct_data`. I'm not sure if this may matter here (that is, if there are optimization opportunities), though. – mafso Oct 04 '14 at 20:30
  • But why these complicated quirks? Put the complete structure specifier in the header (you said re-compilation isn't a problem, so I see no reason not to do so) and you're done. If you don't want the members to be accessed from outside, just don't document them. I had to look at the code for a few minutes to see what it does, if it is safe (and I still have my doubts), if there are parts which when other parts are changed, also need change etc. The full structure in the header would have taken less than a second for me to recognize what it does. – mafso Oct 04 '14 at 20:31