C语言 不透明的 C 结构:它们应该如何声明?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3965279/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-02 06:44:08  来源:igfitidea点击:

Opaque C structs: how should they be declared?

ccoding-stylestructtypedefopaque-pointers

提问by splicer

I've seen both of the following two styles of declaring opaque types in C APIs. Is there any clear advantage to using one style over the other?

我已经看到以下两种在 C API 中声明不透明类型的风格。使用一种风格比另一种风格有什么明显的优势吗?

Option 1

选项1

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

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

Option 2

选项 2

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

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

回答by R.. GitHub STOP HELPING ICE

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

我的投票是支持 mouviciel 发布然后删除的第三个选项:

I have seen a third way:

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

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

我见过第三种方式:

// 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 structkeyword, typedef struct foo foo;(note: get rid of the useless and problematic underscore) is acceptable. But whatever you do, neveruse typedefto 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.

如果您实在受不了输入struct关键字,typedef struct foo foo;(注意:去掉无用且有问题的下划线)是可以接受的。但是无论您做什么,都不要使用typedef为指针类型定义名称。它隐藏了一条极其重要的信息,即这种类型的变量引用一个对象,只要您将它们传递给函数,就可以修改该对象,并且它使处理const指针的不同限定(例如,-限定)版本成为一个主要的痛苦。

回答by Eric Towers

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

bar(const fooRef)声明一个不可变地址作为参数。 bar(const foo *)声明一个不可变的 foo 的地址作为参数。

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 cansidestep 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 fooRefis part of the invariant interface and that foocan come, go, be altered, whatever. The option 2 library writer perceives that foois part of the invariant interface.)

出于这个原因,我倾向于选择选项 2。即,所呈现的接口类型是一种可以在每个间接级别指定 cv-ness 的接口类型。当然,您可以避开选项 1 库编写器而直接使用foo,当库编写器更改实现时,您将面临各种恐惧。(即,选项 1 库编写者仅认为这fooRef是不变接口的一部分,并且foo可以来、去、更改等等。选项 2 库编写者认为这foo是不变接口的一部分。)

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

我更惊讶的是没有人建议组合 typedef/struct 结构。
typedef struct { ... } foo;

回答by Gabriel Staples

Option 1.5 ("Object-based" C Architecture):

选项 1.5(“基于对象的”C 架构):

I am accustomed to using Option 1, except where you name your reference with _hto signify it is a "handle" to a C-style "object" of this given C "class". Then, you ensure your function prototypes use constwherever the content of this object "handle" is an input only, and cannot be changed, and don't use constwherever the content canbe changed.

我习惯于使用Option 1,除非您将引用命名_h为表示它是此给定 C“类”的 C 样式“对象”的“句柄”。然后,您确保您的函数原型const在此对象“句柄”的内容仅为输入且无法更改的const任何地方使用,并且不要在内容可以更改的任何地方使用。

Here's a full example using opaque pointers in C to create objects. The following architecture might be called "object-based C":

这是在 C 中使用不透明指针创建对象的完整示例。以下架构可能被称为“基于对象的 C”:

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

// An opaque pointer (handle) to a C-style "object" of "class" type "my_module" (struct my_module_s *, or my_module_h):
typedef struct my_module_s *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 an input only and cannot modify it
void my_module_do_stuff1(const my_module_h my_module);

// A function that can modify the private content of this "object" (via its handle) (but still cannot modify the 
// handle itself)
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.
// `struct my_module_s`, then points the user's input handle (via its passed-in pointer) to this newly-created "object".
void my_module_close(my_module_h my_module);

//======================================================================================================================
// my_module.c
//======================================================================================================================

// 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)
    my_module = malloc(sizeof(*my_module)); // Dynamically allocate memory for the full contents of the struct "object"
    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:
}

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. 

done:
}

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:
}

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:
}

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 voidtype in all of the functions above and below, return a my_module_error_terror type instead!

  2. Add a configuration struct called my_module_config_tto the .h file, and pass it in to the openfunction to update internal variables when you create a new object. 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)
        my_module = malloc(sizeof(*my_module)); // Dynamically allocate memory for the full contents of the struct "object"
        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.
        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 to the user, and exit.
        *my_module_h_p = my_module;
    
    done:
        return err;
    }
    
  1. 实现完整的错误处理并返回错误而不是void. 前任:

    /// @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;
    

    现在,不是void在上面和下面的所有函数中返回一个my_module_error_t类型,而是返回一个错误类型!

  2. 添加一个调用my_module_config_t.h 文件的配置结构,并将其传递给open函数以在创建新对象时更新内部变量。例子:

    //--------------------
    // 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)
        my_module = malloc(sizeof(*my_module)); // Dynamically allocate memory for the full contents of the struct "object"
        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.
        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 to the user, and exit.
        *my_module_h_p = my_module;
    
    done:
        return err;
    }
    

Additional reading on object-based C architecture:

关于基于对象的 C 架构的补充阅读:

  1. Providing helper functions when rolling out own structures
  1. 在推出自己的结构时提供辅助功能

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

goto对专业代码的错误处理的有效使用的附加阅读和理由:

  1. An argument in favor of the use of gotoin 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 gotoin 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. Error handling in C code
  1. 支持使用gotoC 进行错误处理的论据:https: //github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles/blob/master/Research_General/goto_for_error_handling_in_C/readme.md
  2. *****优秀文章展示了goto在 C 中的错误处理中使用的优点:“在 C 中使用 goto 进行错误处理” - https://eli.thegreenplace.net/2009/04/27/using-goto-for- c 中的错误处理
  3. 在 C 中有效使用 goto 进行错误管理?
  4. C 代码中的错误处理