2

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

Acorn
  • 22,093
  • 4
  • 30
  • 62
Evox402
  • 51
  • 6
  • 1
    The opaque version of the pointer has the type `void *`, which is casted to the real pointer type when it is needed. – Ctx Jan 31 '20 at 14:54
  • 1
    [Here is a complete example.](https://stackoverflow.com/a/29121847/584518). For embedded systems (and MISRA-C), you can't use malloc though, but you have to implement your own static memory pool, [example](https://stackoverflow.com/a/54999410/584518). – Lundin Jan 31 '20 at 15:41
  • 1
    "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." It makes sense when you implement somewhat complex ADTs, especially if part of HAL, with portability considerations. For example I use opaque type when I make library-quality, cross-platform CAN bus HALs. The opaque struct will then contain the specific hardware peripheral data and is placed in the specific driver, on a lower level than the HAL. -> – Lundin Jan 31 '20 at 15:47
  • 1
    For simpler stuff like project-specific SPI & UART etc, I usually don't bother with HAL and opaque types but just hardcode the whole thing with zero portability and code re-use. To make the call when to use it and when not to comes with system design experience, and as such, it's fairly subjective. As for MISRA-C having a directive for this, the main purpose is to educate and make you aware. Please note that Dir 4.8 is advisory, so you can apply it on case basis. – Lundin Jan 31 '20 at 15:48
  • Thanks for the answers, as my project isn't that big and we do not need such a scale of independence, I will see if we can make an exception of this rule as Lundin hinted :) – Evox402 Feb 03 '20 at 05:44

3 Answers3

4

My main point of confusion is where the data of the struct object will?

The point is that you do not use the struct representation (i.e. its size, fields, layout, etc.) in other translation units, but rather call functions that do the work for you. You need to use an opaque pointer for that, yes.

how can I achieve this in my simple example?

You have to put all the functions that use the struct fields (the real struct) in one file (the implementation). Then, in a header, expose only the interface (the functions that you want users to call, and those take an opaque pointer). Finally, users will use the header to call only those functions. They won't be able to call any other function and they won't be able to know what is inside the struct, so code trying to do that won't compile (that is the point!).

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.

It is a way to force modules to be independent of each other. Sometimes it is used to hide implementations to customers or to be able to guarantee ABI stability.

But yes, for internal usage, it is usually a burden (and hinders optimization, since everything becomes a black box to the compiler except if you use LTO etc.). A syntactic approach like public/private in other languages like C++ is way better for that.

However, if you are bound to follow MISRA to such degree (i.e. if your project has to follow that rule, even if it is only advisory), there is not much you can do.

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?

That is up to you. There are very big projects that do not follow that advice and are successful. Typically a comment for private fields, or a naming convention, is enough.

Acorn
  • 22,093
  • 4
  • 30
  • 62
  • It's not really necessary to have all functions thar use the struct fields in one file, which can become quite huge in complex cases. Alternatively you can have the struct definition in an internal header that isn't made public. – Ingo Leonhardt Jan 31 '20 at 15:48
  • "However, if you are bound to follow MISRA, there is not much you can do." The directive regarding opaque type is advisory - the main purpose is to educate programmers. You can be MISRA compliant without using opaque type. – Lundin Jan 31 '20 at 15:54
  • @Lundin Indeed, I will clarify what I meant. – Acorn Jan 31 '20 at 16:25
  • @IngoLeonhardt Of course, but I am simplifying here since OP does not understand the compilation model and his/her "simple example" is just about a few functions, I would presume. – Acorn Jan 31 '20 at 16:28
  • Thank you for your answers :) I see the point of this technique now. But I will check again if I need to be 100% complaint or if I can make an exception with this rule and double check how "clean" our current method is. – Evox402 Feb 03 '20 at 05:41
2

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
}
dbush
  • 162,826
  • 18
  • 167
  • 209
  • Thank you, this helped me to get a better understanding of the concept :) – Evox402 Feb 03 '20 at 05:39
  • I used the same example to run the code, but gcc test.c (the main file) gives me undefined reference errors on all functions. What did I do wrong? – trail99 May 18 '20 at 16:21
  • 1
    @trail99 You need to link the multiple source files together, i.e. `gcc -o myprog test.c struct_test.c` – dbush May 18 '20 at 16:23
  • Note that the proposed approach uses dynamic memory allocation violates MISRA-C Directive 4.12. To the best of my knowledge, its a matter of "picking your poison" when it comes to hiding object members. – Jonathon S. Dec 02 '20 at 22:26
1

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...

The type is not _structName, but struct _structName or (as defined) structType_t.

And my bigger question still remains where the object of my struct is located in memory.

With this technique, there would be a method which returns the address of such an opaque object. It could be statically or dynamically allocated. There should of course also be a method to free an object.

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.

I agree with you.

Armali
  • 14,228
  • 13
  • 47
  • 141