59

CAPIで不透明(OPAQUE)型を宣言する次の2つのスタイルの両方を見てきました。Cで不透明な構造体/ポインタを宣言するさまざまな方法は何ですか?あるスタイルを他のスタイルよりも使用することの明らかな利点はありますか?

オプション1

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

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

オプション2

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

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

4 に答える 4

95

私の投票は、mouvicielが投稿して削除した3番目のオプションに対するものです。

私は3番目の方法を見てきました:

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

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

structキーワードの入力に本当に耐えられない場合はtypedef struct foo foo;(注:役に立たない問題のあるアンダースコアを削除してください)は許容されます。ただし、何をするにしても、ポインタ型の名前を定義するために使用することは絶対にしないでください。typedefこのタイプの変数が、関数に渡すたびに変更される可能性のあるオブジェクトを参照するという非常に重要な情報を隠し、異なる修飾(たとえば、const修飾された)バージョンのポインターを処理することは大きな苦痛になります。

于 2010-10-19T04:23:05.577 に答える
6

オプション1.5(「オブジェクトベース」のCアーキテクチャ):

私はオプション1_hの使用に慣れていますが、参照に名前を付けて、この指定されたC「クラス」のCスタイルの「オブジェクト」への「ハンドル」であることを示す場合を除きます。const次に、このオブジェクト「ハンドル」のコンテンツが入力のみであり、変更できない場所で関数プロトタイプが使用されることを確認し、コンテンツが変更される可能性constがある場所では使用しないようにします。したがって、このスタイルを実行します。

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

void doStuff1(my_module_h my_module);
void doStuff2(const 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
};

これは、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.
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)
    // Dynamically allocate memory for the full contents of the struct "object"
    my_module = 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:
}

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

簡略化された使用例:

#include "my_module.h"

#include <stdbool.h>
#include <stdio.h>

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
        log_errors_and_enter_infinite_loop(); 
    }

    // run the program in this infinite main loop
    while (exit_now == false)
    {
        my_module_do_stuff1(my_module);
        my_module_do_stuff2(my_module);
    }

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

これを超える唯一の改善点は次のとおりです。

  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. .hファイルに呼び出される構成構造体を追加し、my_module_config_tそれを関数に渡してopen、新しいオブジェクトを作成するときに内部変数を更新します。これは、を呼び出すときにクリーンにするために、すべての構成変数を単一の構造体にカプセル化するのに役立ちます_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)
         // Dynamically allocate memory for the full contents of the struct "object"
         my_module = 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;
     }
    

    そして使用法:

     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
    

参照:

  1. [上記の私の答えを参照する私の別の答え] Cで隠されている不透明な構造体とデータへのアーキテクチャ上の考慮事項とアプローチ

オブジェクトベースのCアーキテクチャに関する追加資料:

  1. 独自の構造を展開するときにヘルパー関数を提供する

gotoプロフェッショナルコードのエラー処理の有効な使用法に関する追加の読みと正当化:

  1. gotoエラー処理にCで使用することを支持する議論: https ://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles/blob/master/Research_General/goto_for_error_handling_in_C/readme.md
  2. ***** Cでのエラー処理で使用gotoすることの長所を示す優れた記事:「Cでのエラー処理にgotoを使用する」-https: //eli.thegreenplace.net/2009/04/27/using-goto-for-エラー処理-c
  3. Cでのエラー管理のためのgotoの有効な使用?
  4. Cコードでのエラー処理

よりグーグル化するための検索用語:Cの不透明なポインター、Cの不透明な構造体、Cのtypedef列挙型、Cのエラー処理、cアーキテクチャ、オブジェクトベースのcアーキテクチャ、cの初期化アーキテクチャでの動的メモリ割り当て

于 2019-02-01T23:07:11.263 に答える
1

bar(const fooRef)不変のアドレスを引数として宣言します。 bar(const foo *)不変のfooのアドレスを引数として宣言します。

このため、私はオプション2を好む傾向があります。つまり、提示されるインターフェイスタイプは、間接参照の各レベルでcv-nessを指定できるタイプです。もちろん、オプション1のライブラリライターを回避して使用することもできfooます。ライブラリライターが実装を変更すると、あらゆる種類の恐怖にさらされることになります。(つまり、オプション1のライブラリライターfooRefは、それが不変のインターフェイスの一部であり、foo変更される可能性があることだけを認識します。オプション2のライブラリライターは、それfooが不変のインターフェイスの一部であると認識します。)

typedef/structの組み合わせ構造を提案している人がいないことにもっと驚いています。
typedef struct { ... } foo;

于 2010-10-19T04:34:14.520 に答える
0

オプション3:人々に選択肢を与える

/*  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;
    ...
};

...
于 2022-02-13T06:33:41.417 に答える