How can I hide the declaration of a struct in C?

前端 未结 6 1080
一整个雨季
一整个雨季 2020-12-05 01:13

In the question Why should we typedef a struct so often in C?, unwind answered that:

In this latter case, you cannot return the Point by value, sinc

相关标签:
6条回答
  • 2020-12-05 01:45

    In the header file:

    typedef struct _point * Point;
    

    After the compiler sees this it knows:

    • There is a struct called _point.
    • There is a pointer type Point that can refer to a struct _point.

    The compiler does not know:

    • What the struct _point looks like.
    • What members struct _point contains.
    • How big struct _point is.

    Not only does the compiler not know it - we as programmers don't know it either. This means we can't write code that depends on those properties of struct _point, which means that our code may be more portable.

    Given the above code, you can write functions like:

    Point f() {
       ....
    }
    

    because Point is a pointer and struct pointers are all the same size and the compiler doesn't need to know anything else about them. But you can't write a function that returns by value:

    struct _point f() {
      ....
    }
    

    because the compiler does not know anything about struct _point, specifically its size, which it needs in order to construct the return value.

    Thus, we can only refer to struct _point via the Point type, which is really a pointer. This is why Standard C has types like FILE, which can only be accessed via a pointer - you can't create a FILE structure instance in your code.

    0 讨论(0)
  • 2020-12-05 01:45

    Old question, better answer:

    In Header File:

    typedef struct _Point Point;
    

    In C File:

    struct _Point
    {
       int X;
       int Y;
    };
    
    0 讨论(0)
  • 2020-12-05 01:47

    What that post means is: If you see the header

    typedef struct _Point Point;
    
    Point * point_new(int x, int y);
    

    then you don't know the implementation details of Point.

    0 讨论(0)
  • 2020-12-05 01:49

    What he means is that you cannot return the struct by-value in the header, because for that, the struct must be completely declared. But that happens in the C file (the declaration that makes X a complete type is "hidden" in the C file, and not exposed into the header), in his example. The following declares only an incomplete type, if that's the first declaration of the struct

    struct X;
    

    Then, you can declare the function

    struct X f(void);
    

    But you cannot define the function, because you cannot create a variable of that type, and much less so return it (its size is not known).

    struct X f(void) { // <- error here
      // ...
    }
    

    The error happens because "x" is still incomplete. Now, if you only include the header with the incomplete declaration in it, then you cannot call that function, because the expression of the function call would yield an incomplete type, which is forbidden to happen.

    If you were to provide a declaration of the complete type struct X in between, it would be valid

    struct X;
    struct X f(void);
    
    // ...
    struct X { int data; };
    struct X f(void) { // valid now: struct X is a complete type
      // ...
    }
    

    This would apply to the way using typedef too: They both name the same, (possibly incomplete) type. One time using an ordinary identifier X, and another time using a tag struct X.

    0 讨论(0)
  • 2020-12-05 01:58

    Have a look at this example of a library, using a public header file, a private header file and an implementation file.

    In file public.h:

    struct Point;
    
    struct Point* getSomePoint();
    

    In file private.h:

    struct Point
    {
        int x;
        int y;
    }
    

    In file private.c:

    struct Point* getSomePoint()
    {
        /* ... */
    }
    

    If you compile these three files into a library, you only give public.h and the library object file to the consumer of the library.

    getSomePoint has to return a pointer to Point, because public.h does not define the size of Point, only that is a struct and that it exists. Consumers of the library can use pointers to Point, but can not access the members or copy it around, because they do not know the size of the structure.

    Regarding your further question: You can not dereference because the program using the library does only have the information from private.h, that does not contain the member declarations. It therefore can not access the members of the point structure.

    You can see this as the encapsulation feature of C, just like you would declare the data members of a C++ class as private.

    0 讨论(0)
  • 2020-12-05 02:00

    As an alternative to using opaque pointers (as others have mentioned), you can instead return an opaque bag of bytes if you want to avoid using heap memory:

    // In public.h:
    struct Point
    {
        uint8_t data[SIZEOF_POINT];  // make sure this size is correct!
    };
    void MakePoint(struct Point *p);
    
    // In private.h:
    struct Point
    {
        int x, y, z;
    };
    
    void MakePoint(struct Point *p);
    
    // In private.c:
    void MakePoint(struct Point *p)
    {
        p->x = 1;
        p->y = 2;
        p->z = 3;
    }
    

    Then, you can create instances of the struct on the stack in client code, but the client doesn't know what's in it -- all it knows is that it's a blob of bytes with a given size. Of course, it can still access the data if it can guess the offsets and data types of the members, but then again you have the same problem with opaque pointers (though clients don't know the object size in that case).

    For example, the various structs used in the pthreads library use structs of opaque bytes for types like pthread_t, pthread_cond_t, etc. -- you can still create instances of those on the stack (and you usually do), but you have no idea what's in them. Just take a peek into your /usr/include/pthreads.h and the various files it includes.

    0 讨论(0)
提交回复
热议问题