2

I know this question might have been asked before but I wanted to take my approach at it and get an opinion or possibly a better way to do it. I have three files a.h a.c and main.c Prototypes of functions regarding the struct will be in a.h while implementation will be in a.c and called from main.c the structure will be simple it can just look like this

struct ctx{
    int x;
};

I want a.c to be able to manipulate the contents of the struct but prevent main from having any idea of what's inside. So I figured placing the struct definition inside a.c instead of a.h and placing just struct ctx; as a prototype in a.h And this could work, however ctx can no longer be allocated on the stack in main.c because the compiler doesn't know the size to allocate. So this leads me to my first question: Is there a way to allocate the structure local to the stack without knowing the definition of the structure.

So I assumed if it wasn't possible on the stack then maybe I could just pass it on the heap instead by creating a simple init function that returns a pointer. And that does work but would it be over complicating the process?

a.h

#ifndef a_h
#define a_h

#include <stdio.h>
#include <stdlib.h>
struct ctx;
int incctx(struct ctx* c);
struct ctx* initctx(void);
void destroyctx(struct ctx* c);
#endif /* a_h */

a.c

#include "a.h"
struct ctx{
    int x;
};
int incctx(struct ctx* c){
    return ++c->x;
}
struct ctx* initctx(){
    struct ctx* c =  malloc(sizeof(struct ctx));
    c->x = 0;
    return c;
}
void destroyctx(struct ctx* c){
    free(c);
}

main.c

#include "a.h"

int main(){
    struct ctx* c = initctx();
    printf("%d\n",incctx(c));
    printf("%d\n",incctx(c));
    printf("%d\n",incctx(c));
    destroyctx(c);
    return 0;
}

This design kind of solves the problem with a few drawbacks. 1: What if I wanted to make parts of the structure visible but not the entire thing? 2: If I wanted the structure definition to be available to other files say b.h and b.c would I have to redefined the structure? Do any of you have a cleaner design? I know some people say you can just place a void* in the structure instead of specific types and just label them arbitrary names but I don't see that as a viable solution.

Irelia
  • 3,407
  • 2
  • 10
  • 31
  • Such data-types are called [*opaque data types*](https://en.wikipedia.org/wiki/Opaque_data_type) and are quite common. For a very common example see the standard C `FILE` structure. – Some programmer dude Oct 28 '19 at 18:07
  • I can see the contents of the `FILE` structure though and I can declare it local to the stack. – Irelia Oct 28 '19 at 18:08
  • Do not include `a.h` in main; just define `void *ctx;` – Paul Ogilvie Oct 28 '19 at 18:10
  • You should not be able to create non-pointer instances of the `FILE` type, as `FILE file;`. If you can do that or see members, then that could be a debug-specific extension for your compiler. – Some programmer dude Oct 28 '19 at 18:12
  • Yes strangely enough in my release build this code compiles ` FILE f; fclose(&f);` – Irelia Oct 28 '19 at 18:15
  • @Someprogrammerdude Every compiler I've ever used allows you to see the definition of `FILE`. That's the only way that the `getc` and `putc` macros can work. You *are* correct that there's no point in creating a non-pointer instance of `FILE`, since the only way to get a properly initialized `FILE` struct is by calling `fopen`. – user3386109 Oct 28 '19 at 18:18
  • @user3386109 *Every compiler I've ever used allows you to see the definition of FILE. That's the only way that the `getc` and `putc` macros can work.* Not true. [The 64-bit `FILE` on Solaris is entirely opaque.](https://docs.oracle.com/cd/E37838_01/html/E66175/advanced-43.html#scrolltoc) – Andrew Henle Oct 28 '19 at 18:47
  • @AndrewHenle ... and how did they implement `getc` and `putc`? As macros, or as functions? – user3386109 Oct 29 '19 at 08:40

3 Answers3

5

For the visibility problem you can use two structure in an inheritance-like way.

First you have the public structure that you define in the header file and which your API handles pointers to:

struct ctx
{
    // Public data
};

Then in the source file you create a private structure, where the public structure is the first member:

struct private_ctx
{
    struct ctx ctx;  // The public part of the structure

    // Followed by private data
};

Internally inside the API you use the private_ctx structure, while the code using your API will only use the public ctx structure.

Nested structures like this works similarly to inheritance, the private_ctx structure is a ctx structure. You can create a private_ctx structure and return a pointer to it suitably casted to a ctx structure.

Here's an example on how to create the structure:

struct ctx *create_struct(void)
{
    // Allocate the private structure, which contains the public structure
    struct private_ctx *data = = malloc(sizeof *data);

    // Return the public part of the structure
    return (struct ctx *) data;
}

Using the private data is equally easy with a reverse cast:

void use_data(struct ctx *data)
{
    struct private_ctx *private_data = (struct private_ctx *) data;

    // Here the private data can be used...
}
Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
  • So I would calculate the offset myself from the public ctx structure if it's passed as an argument to get to the private structure. Like `c+=sizeof(struct ctx)` – Irelia Oct 28 '19 at 18:14
  • @Nina No need. Updated my answer. – Some programmer dude Oct 28 '19 at 18:22
  • The problem with this solution is that someone is going to write `struct ctx *myCtx = malloc( sizeof( *myCtx ) );`, pass the **public** version of the `struct` as `useData( myCtx );` and then things will blow up. – Andrew Henle Oct 28 '19 at 18:29
4

Abstraction hiding can be achieved by letting your module hand-out pointers to the struct to main and let your module do all the operations on it. Then it is sufficient that main only knows that ctx is some void data type (pointer), e.g.

// main.c

void *initctx(void);
int incctx(void *c);

int main(void)
{
    void *ctx= initctx();
    int i= incctx(ctx);
    //....
}
machine_1
  • 4,266
  • 2
  • 21
  • 42
Paul Ogilvie
  • 25,048
  • 4
  • 23
  • 41
  • So keep the implementation in the C file and have all my arguments/returns be void*? – Irelia Oct 28 '19 at 18:17
  • Yes. That is, to my kowledge, the cleanest way. – Paul Ogilvie Oct 28 '19 at 18:18
  • What if I want one member of the struct to be public though. Like a void* data member but everything else private? Where the user knows the type of the structure and can grab their void* data members back during callbacks. I think that's where @Some programmer dude solution is the more viable one. – Irelia Oct 28 '19 at 18:18
  • 1
    Do not make it public. Use the so-called "getters" and "setters" that get and set a particular data element, e.g. `int ctxGetx(void *ctx);` and `void ctxSetx(void *ctx, int x);` – Paul Ogilvie Oct 28 '19 at 18:20
  • Ah I see, that's an interesting way to do it. Thank you for the suggestion! – Irelia Oct 28 '19 at 18:21
  • This also has the advantage that any changes will be limited to your module and have not impact on other code. – Paul Ogilvie Oct 28 '19 at 18:23
0

You want to hide the whole struct inside a.c

When allocating a struct, don't return a pointer to it, rather return a handle, which might be an index into the array of structs you maintain in a.c

If you want to expose any part of the struct to the outside, provide functions like getSomething(int handle) or setSomething(int handle).

open(), returning an int uses this approach as opposed to fopen() which returns a FILE *

C.B.
  • 208
  • 2
  • 7