cstructmisrainformation-hidingopaque-pointers

C struct information hiding (Opaque pointer)


I'm currently a bit confused regarding the concept of information hiding of C-structs.

The backround of this question is an embedded c project with nearly zero knowledge of OOP.

Up until now I always declared my typedef structs inside the header file of the corresponding module. So every module which wants to use this struct knows the struct type.

But after a MISRA-C check I discovered the medium severity warning: MISRAC2012-Dir-4.8 - The implementation of a structure is unnecessarily exposed to a translation unit.

After a bit of research I discovered the concept of information hiding of C-structs by limiting the visible access of the struct members to private scope.

I promptly tried a simple example which goes like this:

struct_test.h

//struct _structName;

typedef struct _structName structType_t;

struct_test.c

#include "struct_test.h"

typedef struct _structName
{
    int varA;
    int varB;
    char varC;
}structType_t;

main.c

#include "struct_test.h"

structType_t myTest;

myTest.varA = 0;
myTest.varB = 1;
myTest.varC = 'c';

This yields the compiler error, that for main.c the size of myTest is unknown. And of course it is, main.c has only knowledge that a struct of the type structType_t exists and nothing else.

So I continued my research and stumbled upon the concept of opaque pointers.

So I tried a second attempt:

struct_test.h

typedef struct _structName *myStruct_t;

struct_test.c

#include "struct_test.h"

typedef struct _structName
{
    int varA;
    int varB;
    char varC;
}structType_t;

main.c

#include "struct_test.h"

myStruct_t myTest;

myTest->varA = 1;

And I get the compiler error: dereferencing pointer to incomplete type struct _structName

So obviously I haven't understood the basic concept of this technique. My main point of confusion is where the data of the struct object will?

Up until now I had the understanding that a pointer usually points to a "physical" representation of the datatype and reads/writes the content on the corresponding address.

But with the method above, I declare a pointer myTest but never set an address where it should point to.

I took the idea from this post: What is an opaque pointer in C?

In the post it is mentioned, that the access is handled with set/get interface methods so I tried adding one similiar like this:

void setVarA ( _structName *ptr, int valueA )
{
  ptr->varA = valueA;
}

But this also doesn't work because now he tells me that _structName is unknown... So can I only access the struct with the help of additional interface methods and, if yes, how can I achieve this in my simple example?

And my bigger question still remains where the object of my struct is located in memory. I only know the pointer concept:

varA - Address: 10 - Value: 1

ptrA - Address: 22 - Value: 10

But in this example I only have

myTest - Address: xy - Value: ??

I have trouble understanding where the "physical" representation of the corresponding myTest pointer is located?

Furthermore I can not see the benefits of doing it like this in relatively small scope embedded projects where I am the producer and consumer of the modules.

Can someone explain me if this method is really reasonable for small to mid scale embedded projects with 1-2 developers working with the code? Currently it seems like more effort to make all this interface pointer methods than just declaring the struct in my header-file.

Thank you in advance


Solution

  • As you've deduced, when using an opaque type such as this the main source file can't access the members of the struct, and in fact doesn't know how big the struct is. Because of this, not only do you need accessor functions to read/write the fields of the struct, but you also need a function to allocate memory for the struct, since only the library source knows the definition and size of the struct.

    So your header file would contain the following:

    typedef struct _structName structType_t;
    
    structType_t *init();
    void setVarA(structType_t *ptr, int valueA );
    int getVarA(structType_t *ptr);
    void cleanup(structType_t *ptr);
    

    This interface allows a user to create an instance of the struct, get and set values, and clean it up. The library source would look like this:

    #include "struct_test.h"
    
    struct _structName
    {
        int varA;
        int varB;
        char varC;
    };
    
    structType_t *init()
    {
        return malloc(sizeof(structType_t ));
    }
    
    void setVarA(structType_t *ptr, int valueA )
    {
        ptr->varA = valueA;
    }
    
    int getVarA(structType_t *ptr)
    {
        return ptr->varA;
    }
    
    void cleanup(structType_t *ptr)
    {
        free(ptr);
    }
    

    Note that you only need to define the typedef once. This both defines the type alias and forward declares the struct. Then in the source file the actual struct definition appears without the typedef.

    The init function is used by the caller to allocate space for the struct and return a pointer to it. That pointer can then be passed to the getter / setter functions.

    So now your main code can use this interface like this:

    #include "struct_test.h"
    
    int main()
    {
        structType_t *s = init();
        setVarA(s, 5);
        printf("s->a=%d\n", getVarA(s));
        cleanup(s);l
    }