64

I've seen both of the following two styles of declaring opaque types in C APIs. What are the various ways to declare opaque structs/pointers in C? Is there any clear advantage to using one style over the other?

Option 1

// foo.h
typedef struct foo * fooRef;
void doStuff(fooRef f);

// foo.c
struct foo {
    int x;
    int y;
};

Option 2

// foo.h
typedef struct _foo foo;
void doStuff(foo *f);

// foo.c
struct _foo {
    int x;
    int y;
};
4
  • 4
    See also Is it a good idea to typedef pointers? Commented Jun 5, 2016 at 22:26
  • 1
    Also note that names starting with an underscore are not a good idea in user code (as opposed to system code — the implementation). §7.1.3 "Reserved identifiers" of the standard: • All identifiers that begin with an underscore and either an uppercase letter or another underscore are always reserved for any use. • All identifiers that begin with an underscore are always reserved for use as identifiers with file scope in both the ordinary and tag name spaces. Commented Jun 5, 2016 at 22:29
  • Opaque type example
    – mihai
    Commented Nov 15, 2016 at 7:49
  • (A little late to the party, I know, but) I just proposed a full example as Option 1.5, here: stackoverflow.com/a/54488289/4561887. Commented Feb 1, 2019 at 23:09

4 Answers 4

110

My vote is for the third option that mouviciel posted then deleted:

I have seen a third way:

// foo.h
struct foo;
void doStuff(struct foo *f);

// foo.c
struct foo {
    int x;
    int y;
};

If you really can't stand typing the struct keyword, typedef struct foo foo; (note: get rid of the useless and problematic underscore) is acceptable. But whatever you do, never use typedef to define names for pointer types. It hides the extremely important piece of information that variables of this type reference an object which could be modified whenever you pass them to functions, and it makes dealing with differently-qualified (for instance, const-qualified) versions of the pointer a major pain.

13
  • 10
    'Never' is rather strong here: the whole point of opaque types is to hide the implementation from users of your api, making changes to the former independant of the latter, and providing a measure of safety by restricting direct modifications by the user; I see nothing wrong with aliasing pointer types or hiding qualifiers in such cases (ie if they are implementation details)
    – Christoph
    Commented Oct 19, 2010 at 8:08
  • 40
    Whether a type is a pointer or not is not an implementation detail. It's fundamental to the semantics of any operation in which you might use the type. This is one 'never' I stand by completely. Commented Oct 19, 2010 at 17:11
  • 5
    A type with a builtin const qualifier is not valid for immutable strings (or any allocated object) because your implementation of the object cannot free a const-qualified pointer (free takes a non-const-qualified void *, for good reason). This is not a technicality but a matter of violating the semantics of const. Sure you can cast the const away in your immutable_string_free function, but now we're getting into the territory of dirty hacks. Any opaque object allocation function should always return footype *, and the function to free should take footype *. Commented Oct 20, 2010 at 14:51
  • 20
    @R: Whether a type is a pointer or not absolutely is an implementation detail. Yes, being a pointer gives it certain semantics, but those semantics are not peculiar to pointers. If I expose a handle type from my library, and tell you that it persistently identifies a gadget, you do not, should not, and must not care if it is a pointer or an index into a private global array (or linked-list, to allow growth) inside my library, or magic. The only thing that matters is that it is properly documented as being an identifier for a persistent object.
    – Ben Voigt
    Commented Dec 5, 2010 at 1:08
  • 6
    @Eric: Top-level const gets removed from the actual parameter, so neither "const pointer to magic" nor "const magic" restrict the library in any way whatsoever. And whether it's a "pointer to const magic" or a "pointer to non-const magic" is an implementation detail... it's not important to the caller's code in the least, because he's not supposed to touch the magic, not even supposed to dereference the pointer which is a necessary first step in touching the magic.
    – Ben Voigt
    Commented Dec 10, 2010 at 4:03
12

"Object-based" C Architecture

Quick summary

Assuming you have this .c file:

// -------------
// my_module.c
// -------------

#include "my_module.h"

// Definition of the opaque struct "object" of C-style "class" "my_module".
struct my_module_s
{
    int int1;
    int int2;
    float f1;
    // etc. etc--add more "private" member variables as you see fit
};

These are two beautiful and popular opaque pointer styles:

  1. Recommended technique 1: _h typedef to a pointer to a struct

    // -------------
    // my_module.h
    // -------------
    
    #pragma once
    
    typedef struct my_module_s *my_module_h;
    typedef const struct my_module_s *const_my_module_h;
    
    // This function `malloc`s memory for your struct, and assigns the address
    // of that memory to the passed-in pointer.
    void my_module_open(my_module_h * my_module_h_p);
    // This function can only read the contents of the underlying struct.
    void my_module_do_stuff1(const_my_module_h my_module);
    // This function can read and modify the contents of the underlying struct.
    void my_module_do_stuff2(my_module_h my_module);
    // Free the memory allocated by `my_module_open()`.
    void my_module_close(my_module_h my_module);
    
  2. Recommended technique 2: _t typedef to a struct

    // -------------
    // my_module.h
    // -------------
    
    #pragma once
    
    typedef struct my_module_s my_module_t;
    
    // This function `malloc`s memory for your struct, and assigns the address
    // of that memory to the passed-in pointer.
    void my_module_open(my_module_t ** my_module_p);
    // This function can only read the contents of the underlying struct.
    void my_module_do_stuff1(const my_module_t* my_module);
    // This function can read and modify the contents of the underlying struct.
    void my_module_do_stuff2(my_module_t* my_module);
    // Free the memory allocated by `my_module_open()`.
    void my_module_close(my_module_t* my_module);
    

Technically, you can even mix the two if you want, but for consistency, I recommend against that. Choose one and go with it.

Details

I am accustomed to using Option 1 from the question, except where you name your reference with _h (ex: my_module_h) to signify it is a "handle" to a C-style "object" of this given C "class".

You can use a special const form of the handle (ex: const_my_module_h) wherever you want the handle object to be a non-modifiable, read-only input to a function.

So, do this style:

// -------------
// my_module.h
// -------------

#pragma once

// An opaque pointer (handle) to a C-style "object" of "class" type "my_module"
typedef struct my_module_s *my_module_h;
// Same as above, but an opaque pointer to a const struct instead, so you can't
// modify the contents of the struct. 
typedef const struct my_module_s *const_my_module_h;

// This function can only read the contents of the underlying struct.
void my_module_do_stuff1(const_my_module_h my_module);

// This function can read and modify the contents of the underlying struct.
void my_module_do_stuff2(my_module_h my_module);

// -------------
// my_module.c
// -------------

// Definition of the opaque struct "object" of C-style "class" "my_module".
struct my_module_s
{
    int int1;
    int int2;
    float f1;
    // etc. etc--add more "private" member variables as you see fit
};

Note that the main answer says:

But whatever you do, never use typedef to define names for pointer types.

I only partially agree with this. I would say: never use typedef to define names for pointer types when you use the same name as the struct or _t, such as my_module or my_module_t. For one, my_module should never be a typedef name, in my opinion, because I'd like to write my_type my_variable as my_module_t my_module, so my_module mustn't be the typedef name. And my_module_t should only be a typedefed name to a struct, not to a pointer to a struct, because, like @R.. GitHub STOP HELPING ICE implied, it confusingly hides the fact that it's a pointer.

However, when you use _h instead of _t at the end, this is a common pattern that can mean "handle", where a handle can be a pointer to an opaque struct. So, this is just fine in my opinion, because again, _h is a reasonable exception that may imply pointer usage for this design pattern.

However, I'm not a die-hard _h handle fan. I am perfectly fine with solutions where you see the pointer * too. Since not having to type struct is one of the best features of C++ 😛, however, I really don't want to have to type struct. So, if you really want to see the * to know it's a pointer, do this instead:

// -------------
// my_module.h
// -------------

#pragma once

typedef struct my_module_s my_module_t;

// This function can only read the contents of the underlying struct.
void my_module_do_stuff1(const my_module_t* my_module);

// This function can read and modify the contents of the underlying struct.
void my_module_do_stuff2(my_module_t* my_module);

// -------------
// my_module.c
// -------------

// Definition of the opaque struct "object" of C-style "class" "my_module".
struct my_module_s
{
    int int1;
    int int2;
    float f1;
    // etc. etc--add more "private" member variables as you see fit
};

Full opaque pointer module example

Here's a full example using opaque pointers in C to create objects. The following architecture might be called "object-based C". You can see and run a full example of this from my eRCaGuy_hello_world repo in these 3 files here:

  1. opaque_pointer_demo_main.c
  2. opaque_pointer_demo_module.c
  3. opaque_pointer_demo_module.h

Run commands:

# As C
./opaque_pointer_demo_main.c
# OR (same thing)
gcc -Wall -Wextra -Werror -O3 -std=gnu17 opaque_pointer_demo_main.c opaque_pointer_demo_module.c -o bin/a -lm && bin/a

# As C++
g++ -Wall -Wextra -Werror -O3 -std=gnu++17 opaque_pointer_demo_main.c opaque_pointer_demo_module.c -o bin/a && bin/a

Example run and output:

eRCaGuy_hello_world$ c/opaque_pointer_demo_main.c 
Hello World
my_module_open() done
my_module->my_private_int1 = 0
my_module_do_stuff1() done
my_module_do_stuff2() done
my_module->my_private_int1 = 7
my_module_do_stuff1() done
my_module_do_stuff2() done
my_module_close() done

Source code:

//==============================================================================
// my_module.h
//==============================================================================

#pragma once

// See my answer: https://stackoverflow.com/a/54488289/4561887

// An opaque pointer (handle) to a C-style "object" of "class" type "my_module"
typedef struct my_module_s *my_module_h;
// Same as above, but an opaque pointer to a const struct instead, so you can't
// modify the contents of the struct. 
typedef const struct my_module_s *const_my_module_h;

// Create a new "object" of "class" "my_module": A function that takes a
// **pointer to** an "object" handle, `malloc`s memory for a new copy of the
// opaque  `struct my_module_s`, then points the user's input handle (via its
// passed-in pointer) to this newly-created  "object" of "class" "my_module".
void my_module_open(my_module_h * my_module_h_p);

// A function that takes this "object" (via its handle) as a read-only object.
void my_module_do_stuff1(const_my_module_h my_module);

// A function that takes this "object" (via its handle) as a read-write object.
void my_module_do_stuff2(my_module_h my_module);

// Destroy the passed-in "object" of "class" type "my_module": A function that
// can close this object by stopping all operations, as required, and `free`ing
// its memory.
void my_module_close(my_module_h my_module);
//==============================================================================
// my_module.c
//==============================================================================

#include "opaque_pointer_demo_module.h"

#include <stdio.h>   // For `printf()`
#include <stdlib.h>  // For malloc() and free()
#include <string.h>  // For memset()


// Definition of the opaque struct "object" of C-style "class" "my_module".
// - NB: Since this is an opaque struct (declared in the header but not defined
//   until the source file), it has the following 2 important properties:
// 1) It permits data hiding, wherein you end up with the equivalent of a C++
//    "class" with only *private* member variables.
// 2) Objects of this "class" can only be dynamically allocated. No static
//    allocation is possible since any module including the header file does not
//    know the contents of **nor the size of** (this is the critical part) this
//    "class" (ie: C struct).
struct my_module_s
{
    int my_private_int1;
    int my_private_int2;
    float my_private_float;
    // etc. etc--add more "private" member variables as you see fit
};

void my_module_open(my_module_h * my_module_h_p)
{
    // Ensure the passed-in pointer is not NULL (since it is a core
    // dump/segmentation fault to try to dereference a NULL pointer)
    if (!my_module_h_p)
    {
        // Print some error or store some error code here, and return it at the
        // end of the function instead of returning void.
        goto done;
    }

    // Now allocate the actual memory for a new my_module C object from the
    // heap, thereby dynamically creating this C-style "object".
    my_module_h my_module; // Create a local object handle (pointer to a struct)
    // Dynamically allocate memory for the full contents of the struct "object"
    my_module = (my_module_h)malloc(sizeof(*my_module)); 
    if (!my_module) 
    {
        // Malloc failed due to out-of-memory. Print some error or store some
        // error code here, and return it at the end of the function instead of
        // returning void.   
        goto done;
    }

    // Initialize all memory to zero (OR just use `calloc()` instead of
    // `malloc()` above!)
    memset(my_module, 0, sizeof(*my_module));

    // Now pass out this object to the user, and exit.
    *my_module_h_p = my_module;

done:
    printf("my_module_open() done\n"); // for demo purposes only
}

void my_module_do_stuff1(const_my_module_h my_module)
{
    // Ensure my_module is not a NULL pointer.
    if (!my_module)
    {
        goto done;
    }

    // Do stuff where you use my_module private "member" variables. Ex: use
    // `my_module->my_private_int1` here, or `my_module->my_private_float`, etc. 
    printf("my_module->my_private_int1 = %i\n", my_module->my_private_int1);

    // Now try to modify the variable. This results in the following compile-time
    // error since this function takes `const_my_module_h`:
    //
    // In C:
    // 
    //      eRCaGuy_hello_world/c$ ./opaque_pointer_demo_main.c 
    //      ./opaque_pointer_demo_module.c: In function ‘my_module_do_stuff1’:
    //      ./opaque_pointer_demo_module.c:99:32: error: assignment of member ‘my_private_int1’ in read-only object
    //         99 |     my_module->my_private_int1 = 8;
    //            |                                ^
    //      
    // In C++:
    // 
    //      eRCaGuy_hello_world/c$ g++ -Wall -Wextra -Werror -O3 -std=gnu++17 opaque_pointer_demo_main.c opaque_pointer_demo_module.c -o bin/a && bin/a
    //      opaque_pointer_demo_module.c: In function ‘void my_module_do_stuff1(const_my_module_h)’:
    //      opaque_pointer_demo_module.c:99:32: error: assignment of member ‘my_module_s::my_private_int1’ in read-only object
    //         99 |     my_module->my_private_int1 = 8;
    //            |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~
    //      
    //
    // my_module->my_private_int1 = 8; 

done:
    printf("my_module_do_stuff1() done\n"); // for demo purposes only
}

void my_module_do_stuff2(my_module_h my_module)
{
    // Ensure my_module is not a NULL pointer.
    if (!my_module)
    {
        goto done;
    }

    // Do stuff where you use AND UPDATE my_module private "member" variables.
    // Ex:
    my_module->my_private_int1 = 7;
    my_module->my_private_float = 3.14159;
    // Etc.

done:
    printf("my_module_do_stuff2() done\n"); // for demo purposes only
}

void my_module_close(my_module_h my_module)
{
    // Ensure my_module is not a NULL pointer.
    if (!my_module)
    {
        goto done;
    }

    free(my_module);

done:
    printf("my_module_close() done\n"); // for demo purposes only
}

Simplified example usage:

///usr/bin/env ccache gcc -Wall -Wextra -Werror -O3 -std=gnu17 "$0" "$(dirname "$0")/opaque_pointer_demo_module.c" -o /tmp/a -lm && /tmp/a "$@"; exit
// For the line just above, see my answer here: https://stackoverflow.com/a/75491834/4561887

// local includes
#include "opaque_pointer_demo_module.h"

// 3rd party includes
// NA

// C library includes
#include <stdbool.h> // For `true` (`1`) and `false` (`0`) macros in C
#include <stdint.h>  // For `uint8_t`, `int8_t`, etc.
#include <stdio.h>   // For `printf()`

// int main(int argc, char *argv[])  // alternative prototype
int main()
{
    printf("Hello World\n");

    bool exit_now = false;

    // setup/initialization
    my_module_h my_module = NULL;
    // For safety-critical and real-time embedded systems, it is **critical**
    // that you ONLY call the `_open()` functions during **initialization**, but
    // NOT during normal run-time, so that once the system is initialized and
    // up-and-running, you can safely know that no more dynamic-memory
    // allocation, which is non-deterministic and can lead to crashes, will
    // occur.
    my_module_open(&my_module);
    // Ensure initialization was successful and `my_module` is no longer NULL.
    if (!my_module)
    {
        // await connection of debugger, or automatic system power reset by
        // watchdog
        printf("ERROR: my_module_open() failed!\n");  // for demo purposes only
        // log_errors_and_enter_infinite_loop(); 
    }

    // run the program in this infinite main loop
    while (exit_now == false)
    {
        static size_t counter = 0;

        my_module_do_stuff1(my_module);
        my_module_do_stuff2(my_module);

        // exit after 2 iterations, for demo purposes only
        counter++;
        if (counter >= 2)
        {
            exit_now = true;
        }
    }

    // program clean-up; will only be reached in this case in the event of a
    // major system problem, which triggers the infinite main loop above to
    // `break` or exit via the `exit_now` variable
    my_module_close(my_module);

    // for microcontrollers or other low-level embedded systems, we can never
    // return, so enter infinite loop instead
    //
    // while (true) {}; // await reset by watchdog

    return 0;
}

Improvements: using enum error handling, and a configuration struct

The only improvements beyond this would be to:

  1. Implement full error handling and return the error instead of void. Ex:

    /// @brief my_module error codes
    typedef enum my_module_error_e
    {
        /// No error
        MY_MODULE_ERROR_OK = 0,
    
        /// Invalid Arguments (ex: NULL pointer passed in where a valid 
        /// pointer is required)
        MY_MODULE_ERROR_INVARG,
    
        /// Out of memory
        MY_MODULE_ERROR_NOMEM,
    
        /// etc. etc.
        MY_MODULE_ERROR_PROBLEM1,
    } my_module_error_t;
    

    Now, instead of returning a void type in all of the functions above and below, return a my_module_error_t error type instead!

  2. Add a configuration struct called my_module_config_t to the .h file, and pass it in to the open function to update internal variables when you create a new object. This helps encapsulate all configuration variables in a single struct for cleanliness when calling _open().

    Example:

    //--------------------
    // my_module.h
    //--------------------
    
    // my_module configuration struct
    typedef struct my_module_config_s
    {
        int my_config_param_int;
        float my_config_param_float;
    } my_module_config_t;
    
    my_module_error_t my_module_open(my_module_h * my_module_h_p, 
                                    const my_module_config_t *config);
    
    //--------------------
    // my_module.c
    //--------------------
    
    my_module_error_t my_module_open(my_module_h * my_module_h_p, 
                                    const my_module_config_t *config)
    {
        my_module_error_t err = MY_MODULE_ERROR_OK;
    
        // Ensure the passed-in pointer is not NULL (since it is a core 
        // dump/segmentation fault to try to dereference  a NULL pointer)
        if (!my_module_h_p)
        {
            // Print some error or store some error code here, and return it 
            // at the end of the function instead of returning void. Ex:
            err = MY_MODULE_ERROR_INVARG;
            goto done;
        }
    
        // Now allocate the actual memory for a new my_module C object from 
        // the heap, thereby dynamically creating this C-style "object".
        my_module_h my_module; // Create a local object handle (pointer to a 
                               // struct)
        // Dynamically allocate memory for the full contents of the struct 
        // "object"
        my_module = (my_module_h)malloc(sizeof(*my_module)); 
        if (!my_module) 
        {
            // Malloc failed due to out-of-memory. Print some error or store 
            // some error code here, and return it at the end of the function 
            // instead of returning void. Ex:
            err = MY_MODULE_ERROR_NOMEM;
            goto done;
        }
    
        // Initialize all memory to zero (OR just use `calloc()` instead of 
        // `malloc()` above!)
        memset(my_module, 0, sizeof(*my_module));
    
        // Now initialize the object with values per the config struct passed 
        // in. Set these private variables inside `my_module` to whatever they 
        // need to be. You get the idea...
        my_module->my_private_int1 = config->my_config_param_int;
        my_module->my_private_int2 = config->my_config_param_int*3/2;
        my_module->my_private_float = config->my_config_param_float;        
        // etc etc
    
        // Now pass out this object handle to the user, and exit.
        *my_module_h_p = my_module;
    
    done:
        return err;
    }
    

    And usage:

    my_module_error_t err = MY_MODULE_ERROR_OK;
    
    my_module_h my_module = NULL;
    my_module_config_t my_module_config = 
    {
        .my_config_param_int = 7,
        .my_config_param_float = 13.1278,
    };
    err = my_module_open(&my_module, &my_module_config);
    if (err != MY_MODULE_ERROR_OK)
    {
        switch (err)
        {
        case MY_MODULE_ERROR_INVARG:
            printf("MY_MODULE_ERROR_INVARG\n");
            break;
        case MY_MODULE_ERROR_NOMEM:
            printf("MY_MODULE_ERROR_NOMEM\n");
            break;
        case MY_MODULE_ERROR_PROBLEM1:
            printf("MY_MODULE_ERROR_PROBLEM1\n");
            break;
        case MY_MODULE_ERROR_OK:
            // not reachable, but included so that when you compile with 
            // `-Wall -Wextra -Werror`, the compiler will fail to build if you 
            // forget to handle any of the error codes in this switch statement.
            break;
        }
    
        // Do whatever else you need to in the event of an error, here. Ex:
        // await connection of debugger, or automatic system power reset by 
        // watchdog
        while (true) {}; 
    }
    
    // ...continue other module initialization, and enter main loop
    

See also:

  1. [another answer of mine which references my answer above] Architectural considerations and approaches to opaque structs and data hiding in C
  2. My answer: Error handling in C code - I demonstrate using enums for error handling in C or C++.

Additional reading on object-based C architecture:

  1. Providing helper functions when rolling out own structures

Additional reading and justification for valid usage of goto in error handling for professional code:

  1. An argument in favor of the use of goto in C for error handling: https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles/blob/master/Research_General/goto_for_error_handling_in_C/readme.md
  2. *****EXCELLENT ARTICLE showing the virtues of using goto in error handling in C: "Using goto for error handling in C" - https://eli.thegreenplace.net/2009/04/27/using-goto-for-error-handling-in-c
  3. Valid use of goto for error management in C?
  4. My answer: Error handling in C code
  5. My answer: Why are goto, break, continue and multiple return statements considered bad practice

Search terms to make more googlable: opaque pointer in C, opaque struct in C, typedef enum in C, error handling in C, c architecture, object-based c architecture, dynamic memory allocation at initialization architecture in c

17
  • 2
    This example was almost perfect, until I saw.......goto. Really?
    – Michi
    Commented Jan 5, 2020 at 10:02
  • 3
    Yes, really. I used to be really anti goto too, until I started using it professionally. Now that I've written tons & tons of C code which does long & complicated error checking, I have concluded it is the best way to handle error checking, period, and there is no equivalent alternative which makes code as safe & readable and easy to write as goto does. If only you were here with me we could sit down together & I'd spend 1 hr + with you to go over many many examples where the virtues of goto used in this way (& only this way) really shine through, & I think you'd become a convert & use it too. Commented Jan 5, 2020 at 15:47
  • 1
    @FedericoBaù, this isn't quite true (I understand that goto is something to stay far far away and everybody dislike it,), but it's definitely an area of contention. As I've programmed professionally in both embedded C and application level C++, I've come to realize that professional developers (myself included) become very very opinionated over time. Some professional software developer teams have declared: "goto is the best tool for error handling in C and you SHALL use it." Also, many C developers abhor C++ with a passion, and, many C++ developers abhor C styles in C++ with a passion. Commented Dec 30, 2020 at 17:18
  • 2
    Both of these views: C++ developers hating C styles, and C developers hating C++, are wrong in my opinion. My favorite way to write "C" is to use the C++ compiler, because I can write far more beautiful code that looks like C (but is actually C++) with the C++ compiler than I ever could with the C compiler. Regarding goto: the community is split. goto is mis-taught in school. To say it is evil and should NEVER be used is...well...evil, and should NEVER be said. :) It has its place, when used properly. See my article and other justification in the links in the bottom of my answer. Commented Dec 30, 2020 at 17:19
  • 1
    @Gabriel Staples, it must be the way I express the comment but I was actually agree completely to what you stated, what I meant is that as a beginner in C and learning it I'm exposed to what i Found around the internet in order to learn it, as so far I mostly encountered a bad view regarding the goto(hence my phrase). So I bump into your answer and I actually found interesting (because again, mainly i see around that is "evil"). I believe now that is a tool that is better left when becoming more advanced (so not where I'm currently) Commented Dec 30, 2020 at 17:35
1

bar(const fooRef) declares an immutable address as argument. bar(const foo *) declares an address of an immutable foo as argument.

For this reason, I tend to prefer option 2. I.e., the presented interface type is one where cv-ness can be specified at each level of indirection. Of course one can sidestep the option 1 library writer and just use foo, opening yourself to all sorts of horror when the library writer changes the implementation. (I.e., the option 1 library writer only perceives that fooRef is part of the invariant interface and that foo can come, go, be altered, whatever. The option 2 library writer perceives that foo is part of the invariant interface.)

I'm more surprised that no one's suggested combined typedef/struct constructions.
typedef struct { ... } foo;

2
  • 5
    Regarding your last sentence, these constructions do not admit opaque types. If you use them, you're exposing the definition of the structure in your header for the calling application to abuse. Commented Oct 19, 2010 at 4:36
  • In neither option is the layout of foo part of the interface. That's the whole point of doing things this way.
    – Ben Voigt
    Commented Dec 5, 2010 at 1:09
0

Option 3: Give people choice

/*  foo.h  */

typedef struct PersonInstance PersonInstance;

typedef struct PersonInstance * PersonHandle;

typedef const struct PersonInstance * ConstPersonHandle;

void saveStuff (PersonHandle person);

int readStuff (ConstPersonHandle person);

...


/*  foo.c  */

struct PersonInstance {
    int a;
    int b;
    ...
};

...
1
  • Too much boilerplate
    – CPlus
    Commented Apr 10 at 19:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.