2

I want to hide the struct define, so I define struct in the source file, like this :

//a.c
#include "a.h"

struct a_s
{
   int a;
   int b;
};

int func(a_t *a)
{
   printf("%d\n", a->a);
   return 0;
}


and I declare the struct in the header file, like this:

//a.h
#ifndef TEST
#define TEST
#include <stdio.h>
#include <stddef.h>

typedef struct a_s a_t;
#endif

Then I use the struct a_t int main.c file, like this:

#include "stddef.h"
#include "a.h"

int main()
{
   a_t a;
   a.a =2;
   func(&a);

   return 0;

}

But when I compile the main.c by gcc -c main.c, it failed by

main.c: In function ‘main’:
main.c:7:15: error: storage size of ‘a’ isn’t known
    struct a_s a;

Why is this failing?

Christopher Moore
  • 15,626
  • 10
  • 42
  • 52
sundq
  • 735
  • 2
  • 9
  • 28

3 Answers3

1

If you instantiate an object A a, the linker searches for the definition of A in order for the compiler to know how much memory it needs to allocate. It searches a.h and finds a typedef but no declaration, and so the error is saying that it doesn't know A's size.

If the purpose of the program is to hide the declarations (and definitions) from the users, you will need to use A *a, as this says to the compiler "there is a type A, and memory for it will be stored beginning at this memory location" and so doesn't need any information about the size or layout of the data until runtime where memory should be dynamically allocated and freed.

This approach allows the developers to expose an interface to users, without the users knowing any specifics on how data is structured and allowing the software to be updated and data structures modified all while keeping the outward facing headers the same (and keeping tests passing).

jackw11111
  • 1,457
  • 1
  • 17
  • 34
  • For example, consider the `FILE` type, which is a struct. You call `fopen` to get a pointer to a `FILE` struct. Then you can call functions like `fread` and `fprintf` that take a `FILE *`. And finally, `fclose` to clean up. All that can be done without the caller knowing anything about what's in the `FILE` struct. – user3386109 Dec 04 '19 at 02:36
  • Yes, I think I understand, thank you. Sort of like how closed source software has headers with definitions visible to the end users but source is in .o, .lib files etc... – jackw11111 Dec 04 '19 at 02:38
  • 1
    Exactly. By hiding the internals of the struct, the library maintainers don't have to worry about backwards compatibility when they want to change the struct, or add more features. – user3386109 Dec 04 '19 at 02:40
  • @user3386109 may be I understand, if you want to hide the struct define, The user can only define the pointer of the type, and you must implement a api that create the struct instance(by malloc) – sundq Dec 04 '19 at 02:45
  • 1
    @sundq That's right. You should also have a function that allows the user to `free` the structure. – user3386109 Dec 04 '19 at 02:48
1

if you want to hide the struct define, The user can only define the pointer of the type, and you must implement a api that create the struct instance(by malloc) and a api that release the struct instance(by free)

sundq
  • 735
  • 2
  • 9
  • 28
  • Not only would `a_t a;` need to be replaced, but so would `a.a =2;` You would also need to add appropriate getters and setters to the API too. – ikegami Dec 04 '19 at 22:10
1

You can't create an instance of a struct that hasn't been defined because the compiler doesn't know how much space to allocate for it.

You can't access the members of struct that hasn't been defined because the compiler doesn't know their type.

However, you can use a pointer to a struct that hasn't been defined. This allows one to do something as follows:

foo.h:

typedef struct Foo Foo

Foo* Foo_new(int a, int b);
void Foo_destroy(Foo* this);
void Foo_set_a(Foo* this, int a);
void Foo_set_b(Foo* this, int b);
int Foo_get_a(Foo* this);
int Foo_get_b(Foo* this);
// ...

foo.c:

#include "a.h"

struct Foo {
   int a;
   int b;
};

Foo* Foo_new(int a, int b) {
   Foo* this = malloc(sizeof(Foo));
   this->a = a;
   this->b = b;
   return this;
}

void Foo_destroy(Foo* this) { free(this); }
void Foo_set_a(Foo* this, int a) { this->a = a; }
void Foo_set_b(Foo* this, int b) { this->b = b; }
int Foo_get_a(Foo* this) { return this->a; }
int Foo_get_b(Foo* this) { return this->b; }
// ...

main.c

#include <stdio.h>
#include "foo.h"

int main(void) {
   Foo* foo = Foo_new(3, 4);
   Foo_set_a(foo, 5);
   printf("%d %d\n",
      Foo_get_a(foo),
      Foo_get_b(foo),
   );
   Foo_destroy(foo);
   return 0;
}

You could even include the pointer in the typedef if you wanted a truly opaque type. Normally, that would be a bad practice, but it makes a certain amount in sense in this particular situation. See this for more on this concept.

ikegami
  • 367,544
  • 15
  • 269
  • 518