1

I have the following header file:

struct StackList_s;
typedef struct StackList_s StackList_t;
// From here I add in the method signatures

And the following source file:

struct StackList_s
{
    integer_t count;
    struct StackListNode_s *top; // Here begins the linked list
    // Some other members that store information about the stack
    integer_t version_id;
};
// From here I define StackListNode_s and implement the StackList_s functions
// Note that the user will never manipulate directly a StackListNode_s
// There are functions that will handle the free() of each node correctly

I hide the struct definition in the source file so that anyone using this stack can't modify directly its members, since changing them requires some input treatment or checking for certain invalid states.

Currently, to get a new stack you have to use the following:

// malloc(sizeof(StackList_t)) and set members to default
StackList_t *stack = stl_new(/* Some info parameters */);

But I can only do this allocating a StackList_t in the heap. What I want to do is to have the StackList_t allocated on the stack and then its nodes can be allocated in the heap allong with their data and pointers to other nodes. This way I can give the user a choice, if either the struct is being used locally or if he will pass it around functions as an allocated resource.

StackList_t stack;
stl_init(&stack, /* Info parameters */); // No malloc, only setting members to 0

But of course I can't do this because the definition of struct StackList_s is in the source file. So here are my questions:

  • Is it possible to, at the same time, not allow access to members of a struct and allocate that same struct in the stack?
  • Is there any way to tell the compiler the size of my struct?
LeoVen
  • 632
  • 8
  • 18
  • suggest, in the .c file that contains the struct, to have a related header file and within that .c file have functions to read each field, functions to write each field, etc and to be able to generate a new instance of the struct and return a indication of what instance of the struct to access when accessing the 'getter/setter' functions – user3629249 Mar 13 '19 at 10:39

3 Answers3

2

You can do that with VLAs or alloca in Linux:

Library header:

struct StackList_s;
typedef struct StackList_s StackList_t;
extern const size_t StackList_size;

// If you're using VLAs
extern const size_t StackList_align;
StackList_t* stl_init_inline(char stack_source[], ...);

Library source:

#include "header.h"

struct StackList_s {
    // ...
};

const size_t StackList_size = sizeof(StackList_t);

// If you're using VLAs
#include <stdalign.h>
#include <stdint.h>

const size_t StackList_align = alignof(StackList_t);
StackList_t* stl_init_inline(char stack_source[], ...) {
    // align the address to the nearest multiple of StackList_align
    uintptr_t address = (uintptr_t) ((void*) stack_source);
    if (address % StackList_align != 0) {
        address += StackList_align - address % StackList_align;
    }
    StackList_t* stack = (StackList_t*) ((void*) address);
    stl_init(stack, ...);
    return stack;
}

Main source

#include <header.h>

StackList_t* stack = alloca(Stacklist_size);
stl_init(stack, ...);

char stack_source[StackList_size + StackList_align - 1];  // Not compile time.
StackList_t* stack = stl_init_inline(stack_source, ...);

This would allocate it on the stack, and you won't need to free it, but it's slower and more verbose than just StackList_t stack_source;. (And alloca is Linux only)

For the second question, you need the full definition of a struct to get it's size. Common pitfalls include the fact that sizeof(struct { int a; }) == sizeof(struct { int a; }) can be false. It probably won't be though, so you can do #define StackList_size sizeof(struct { integer_t count; struct StackListNode_s *top; integer_t version_id; }) but that also leads to a lot of code duplication.

I personally would just put the struct definition in the header file, and just declare "don't mess with the members or my methods won't work" in a comment somewhere (Maybe making the names start with _ to give a hint that they are private)

Artyer
  • 31,034
  • 3
  • 47
  • 75
  • This solution has alignment issues. There’s no guaraneee that `stack_source` would start at an address that is suitably aligned for a `Stacklist_t`. – dbush Mar 12 '19 at 22:24
  • so make `stack_source` a `integer_t` and divide StackList_size by the appropriate amount. – AShelly Mar 12 '19 at 22:29
  • [Technically, this isn't guaranteed to be on the stack](https://stackoverflow.com/q/33020346/10396). Technically, the C standard doesn't even mention stacks or heaps. – AShelly Mar 12 '19 at 22:34
1

You could do something similar to Artyer's answer without using VLA's by using a #define instead

Header:

#define STACKLISTSIZE 32
typedef uint8_t stl_storage[STACKLISTSIZE];
typedef struct stacklist_s stacklist_t;

stacklist_t* stl_create_from_stack(stl_storage b);  //user provides memory
stacklist_t* stl_allocate(void);  //library allocates memory, user must free.

Source:

int myfunction()
{
  stl_storage x;
  stacklist_t* sp = stl_create_from_stack(x);
  //do something with sp.
}

Make sure you have a compile-time assert that sizeof(stack_s) == STACKSTRUCTSIZE in the implementation file.

AShelly
  • 34,686
  • 15
  • 91
  • 152
1

Some implementations guarantee that calls between compilation units will be processed in a fashion consistent with the platform's Application Binary Interface (ABI), without regard for what a called function is going to do with storage whose address it receives, or what a caller will have done with storage whose address it supplies, or will do with such storage once the function returns. On such implementations, given something like:

// In header
typedef union FOO_PUBLIC_UNION { 
  uint64_t dat[4]; // Allocate space
  double dummy_align1; // Force alignment
  void *dummy_align2; // Force alignment
} FOO;
void act_on_foo(FOO_PUBLIC_UNION*);

// In code
FOO x = {0};
act_on_foo(&x);

in one compilation unit, and something like:

struct FOO_PRIVATE {
  int this; float that; double whatever;
};

typedef union FOO_PUBLIC_UNION { uint64_t dat[4]; struct FOO_PRIVATE priv; } FOOPP;

void act_on_foo(FOO *p)
{
  FOOPP *pp = (FOOPP*)p;
  pp->priv.whatever = 1234.567;
}

provided that the size of FOO and FOOPP match, the behavior of calling an external function from the first compilation unit would be defined as allocating sizeof(FOO) bytes, zeroing them, and passing their address to act_on_foo, whose behavior would then be defined as acting upon the bytes to which it receives an address, without regard for how they got their values or what the caller would do with them later.

Unfortunately, even though almost every implementation should be capable of producing behavior consistent with calling a function it knows nothing about, there is no standard way of indicating to a compiler that a particular function call should be viewed as "opaque". Implementations intended for purposes where that would be useful could and typically did support such semantics with "ordinary" function calls whether or not the Standard required that, and such semantics would offer little value on implementations intended only for purposes where they wouldn't be useful. Unfortunately, this has led to a Catch 22: there's no reason for the Standard to mandate things implementations would be free to do, with or without a mandate, in cases where they're useful, but some compiler writers treat the Standard's lack of a mandate as an encouragement to deny support.

supercat
  • 77,689
  • 9
  • 166
  • 211