48

組み込みシステム用にプログラミングする場合、malloc()は絶対に許可されないことがよくあります。ほとんどの場合、これに対処することはできますが、イライラすることが1つあります。それは、データの非表示を有効にするために、いわゆる「不透明(OPAQUE)型」を使用できないことです。通常、私は次のようなことをします:

// In file module.h
typedef struct handle_t handle_t;

handle_t *create_handle();
void operation_on_handle(handle_t *handle, int an_argument);
void another_operation_on_handle(handle_t *handle, char etcetera);
void close_handle(handle_t *handle);


// In file module.c
struct handle_t {
    int foo;
    void *something;
    int another_implementation_detail;
};

handle_t *create_handle() {
    handle_t *handle = malloc(sizeof(struct handle_t));
    // other initialization
    return handle;
}

create_handle()はmalloc()を実行して「インスタンス」を作成します。malloc()の必要性を防ぐためによく使用される構造は、create_handle()のプロトタイプを次のように変更することです。

void create_handle(handle_t *handle);

そして、呼び出し元は次のようにハンドルを作成できます。

// In file caller.c
void i_am_the_caller() {
    handle_t a_handle;    // Allocate a handle on the stack instead of malloc()
    create_handle(&a_handle);
    // ... a_handle is ready to go!
}

しかし残念ながら、このコードは明らかに無効であり、handle_tのサイズは不明です。

私はこれを適切な方法で解決するための解決策を実際に見つけたことはありません。誰かがこれを行う適切な方法を持っているかどうか、またはCでデータを隠すことを可能にする完全に異なるアプローチがあるかどうかを知りたいです(もちろん、module.cで静的グローバルを使用しないでください。複数のインスタンスを作成できる必要があります)。

4

10 に答える 10

16

_alloca 関数を使用できます。正確には標準ではないと思いますが、私の知る限り、ほぼすべての一般的なコンパイラがそれを実装しています。デフォルトの引数として使用すると、呼び出し元のスタックから割り当てられます。

// Header
typedef struct {} something;
int get_size();
something* create_something(void* mem);

// Usage
handle* ptr = create_something(_alloca(get_size()); // or define a macro.

// Implementation
int get_size() {
    return sizeof(real_handle_type);
}
something* create_something(void* mem) {
    real_type* ptr = (real_type_ptr*)mem;
    // Fill out real_type
    return (something*)mem;
}

ある種のオブジェクト プール セミヒープを使用することもできます。現在使用可能なオブジェクトの最大数がある場合は、それらにすべてのメモリを静的に割り当て、現在使用されているオブジェクトをビット シフトするだけです。

#define MAX_OBJECTS 32
real_type objects[MAX_OBJECTS];
unsigned int in_use; // Make sure this is large enough
something* create_something() {
     for(int i = 0; i < MAX_OBJECTS; i++) {
         if (!(in_use & (1 << i))) {
             in_use &= (1 << i);
             return &objects[i];
         }
     }
     return NULL;
}

私のビットシフトは少しずれています。私がやったのは久しぶりですが、要点を理解していただければ幸いです。

于 2010-12-14T15:19:54.163 に答える
9

1つの方法は、次のようなものを追加することです

#define MODULE_HANDLE_SIZE (4711)

公開module.hヘッダーに。これにより、これを実際のサイズと同期させるという懸念事項が生じるため、もちろん、ラインはビルド プロセスによって自動生成されるのが最適です。

もう 1 つのオプションはもちろん、実際に構造を公開することですが、不透明であり、定義された API 以外の手段によるアクセスを禁止するものとして文書化します。これは、次のようにすることでより明確にすることができます。

#include "module_private.h"

typedef struct
{
  handle_private_t private;
} handle_t;

ここでは、モジュールのハンドルの実際の宣言が別のヘッダーに移動され、あまり目立たなくなりました。そのヘッダーで宣言された型は、目的の名前でラップされ、typedefプライベートであることを示します。

モジュール内の関数は値としてhandle_t *安全にアクセスできます。これはパブリック構造体の最初のメンバーであるためです。privatehandle_private_t

于 2010-12-14T15:06:59.383 に答える
7

struct handle_tオブジェクトの静的プールを作成し、必要に応じて提供する場合の1つの解決策。これを実現する方法はたくさんありますが、簡単な例を次に示します。

// In file module.c
struct handle_t 
{
    int foo;
    void* something;
    int another_implementation_detail;

    int in_use ;
} ;

static struct handle_t handle_pool[MAX_HANDLES] ;

handle_t* create_handle() 
{
    int h ;
    handle_t* handle = 0 ;
    for( h = 0; handle == 0 && h < MAX_HANDLES; h++ )
    {
        if( handle_pool[h].in_use == 0 )
        {
            handle = &handle_pool[h] ;
        }
    }

    // other initialization
    return handle;
}

void release_handle( handle_t* handle ) 
{
    handle->in_use = 0 ;
}

未使用のハンドルを見つけるためのより高速な方法があります。たとえば、ハンドルが割り当てられるたびに増加する静的インデックスを保持し、MAX_HANDLES に達すると「ラップアラウンド」することができます。これは、いずれかを解放する前に複数のハンドルが割り当てられる典型的な状況では高速になります。ただし、ハンドルの数が少ない場合は、この力ずくの検索でおそらく十分です。

もちろん、ハンドル自体はポインターである必要はありませんが、隠しプールへの単純なインデックスにすることができます。これにより、データの隠蔽と外部アクセスからのプールの保護が強化されます。

したがって、ヘッダーには次のようになります。

typedef int handle_t ;

コードは次のように変更されます。

// In file module.c
struct handle_s 
{
    int foo;
    void* something;
    int another_implementation_detail;

    int in_use ;
} ;

static struct handle_s handle_pool[MAX_HANDLES] ;

handle_t create_handle() 
{
    int h ;
    handle_t handle = -1 ;
    for( h = 0; handle != -1 && h < MAX_HANDLES; h++ )
    {
        if( handle_pool[h].in_use == 0 )
        {
            handle = h ;
        }
    }

    // other initialization
    return handle;
}

void release_handle( handle_t handle ) 
{
    handle_pool[handle].in_use = 0 ;
}

返されたハンドルは内部データへのポインターではなくなり、好奇心旺盛なユーザーや悪意のあるユーザーはハンドルを介してアクセスできなくなるためです。

複数のスレッドでハンドルを取得する場合は、いくつかのスレッド セーフ メカニズムを追加する必要があることに注意してください。

于 2010-12-14T17:15:39.463 に答える
7

残念ながら、この問題に対処する典型的な方法は、単にプログラマーにオブジェクトを不透明なものとして扱わせることだと思います。完全な構造の実装はヘッダーにあり、利用可能です。プログラマーの責任は、内部を直接使用しないことだけです。オブジェクトに定義された API を介して。

これで十分でない場合は、いくつかのオプションが考えられます。

  • C++ を「より良い C」として使用し、構造の内部を として宣言しprivateます。
  • 構造体の内部が宣言されるように、ヘッダーである種のプリプロセッサを実行しますが、名前は使用できません。適切な名前を持つ元のヘッダーは、構造を管理する API の実装で使用できます。私はこのテクニックが使われているのを見たことがありません - それは可能かもしれない私の頭のてっぺんからのアイデアですが、それが価値があるよりもはるかに面倒なようです.
  • 不透明なポインターを使用するコードで、静的に割り当てられたオブジェクトextern(つまり、グローバル) を宣言します。次に、オブジェクトの完全な定義にアクセスできる特別なモジュールで、これらのオブジェクトを実際に宣言します。「特別な」モジュールのみが完全な定義にアクセスできるため、不透明なオブジェクトの通常の使用は不透明なままです。ただし、オブジェクトがグローバルであるという事実を悪用しないように、プログラマーに頼らなければなりません。また、名前の競合の変更も増加したため、管理する必要があります (意図せずに発生する可能性があることを除けば、おそらく大きな問題ではありません。痛い!)。

全体として、プログラマーがこれらのオブジェクトを使用するための規則に従っていることに頼るのが最善の解決策かもしれないと思います (私の意見では、C++ のサブセットを使用することも悪くありません)。構造内部を使用しないという規則に従うようにプログラマに依存することは完全ではありませんが、一般的に使用されている実行可能なソリューションです。

于 2010-12-14T15:52:51.423 に答える
1

構造体を privateTypes.h ヘッダー ファイルに入れるだけです。不透明ではなくなりますが、プライベートファイル内にあるため、プログラマにとってはプライベートになります。

例: C 構造体でメンバーを非表示にする

于 2013-03-12T16:42:06.917 に答える
1

不透明なデータ構造のヘッダーが、操作から操作に引き継がれる必要があるさまざまなデータをすべて保持するデータ構造を実装する際に、同様の問題に直面しました。

再初期化によってメモリ リークが発生する可能性があるため、データ構造の実装自体が、割り当てられたメモリのヒープへのポイントを実際に上書きしないようにしたかったのです。

私がしたことは次のとおりです。

/** 
 * In order to allow the client to place the data structure header on the
 * stack we need data structure header size. [1/4]
**/
#define CT_HEADER_SIZE  ( (sizeof(void*) * 2)           \
                        + (sizeof(int) * 2)             \
                        + (sizeof(unsigned long) * 1)   \
                        )

/**
 * After the size has been produced, a type which is a size *alias* of the
 * header can be created. [2/4] 
**/        
struct header { char h_sz[CT_HEADER_SIZE]; };
typedef struct header data_structure_header;

/* In all the public interfaces the size alias is used. [3/4] */
bool ds_init_new(data_structure_header *ds /* , ...*/);

実装ファイル内:

struct imp_header {
    void *ptr1, 
         *ptr2;
    int  i, 
         max;
    unsigned long total;
};

/* implementation proper */
static bool imp_init_new(struct imp_header *head /* , ...*/)
{
    return false; 
}

/* public interface */
bool ds_init_new(data_structure_header *ds /* , ...*/) 
{
    int i;

    /* only accept a zero init'ed header */
    for(i = 0; i < CT_HEADER_SIZE; ++i) {
        if(ds->h_sz[i] != 0) {
            return false;
        }
    }

    /* just in case we forgot something */
    assert(sizeof(data_structure_header) == sizeof(struct imp_header));

    /* Explicit conversion is used from the public interface to the
     * implementation proper.  [4/4]
     */
    return imp_init_new( (struct imp_header *)ds /* , ...*/); 
}

クライアント側:

int foo() 
{
    data_structure_header ds = { 0 };

    ds_init_new(&ds /*, ...*/);
}
于 2013-05-22T09:40:35.943 に答える
0

私がこれまでに見た中で最も厳しい解決策は、呼び出し元が使用するために不透明な構造体を提供することでした。これは、実際の構造体で使用される型の言及とともに、十分に大きく、おそらく少し大きく、不透明な構造体を保証することです。構造体は、実際のものと比較して十分に整列されます:

struct Thing {
    union {
        char data[16];
        uint32_t b;
        uint8_t a;
    } opaque;
};
typedef struct Thing Thing;

次に、関数はそれらのいずれかへのポインターを取ります。

void InitThing(Thing *thing);
void DoThingy(Thing *thing,float whatever);

内部的には、API の一部として公開されていませんが、真の内部構造を持つ構造体があります。

struct RealThing {
    uint32_t private1,private2,private3;
    uint8_t private4;
};
typedef struct RealThing RealThing;

(これにはuint32_t' anduint8_t' があるだけです。これが、上記の結合でこれら 2 つの型が出現する理由です。)

さらに、おそらくコンパイル時のアサートで、RealThingのサイズが のサイズを超えないようにしThingます。

typedef char CheckRealThingSize[sizeof(RealThing)<=sizeof(Thing)?1:-1];

次に、ライブラリ内の各関数は、引数を使用するときにその引数に対してキャストを行います。

void InitThing(Thing *thing) {
    RealThing *t=(RealThing *)thing;

    /* stuff with *t */
}

これにより、呼び出し元はスタック上に適切なサイズのオブジェクトを作成し、それらに対して関数を呼び出すことができます。構造体はまだ不透明であり、不透明なバージョンが十分に大きいことを確認します。

潜在的な問題の 1 つは、フィールドが実際の構造体に挿入される可能性があることです。これは、不透明な構造体が必要としないアラインメントが必要であることを意味し、これは必ずしもサイズ チェックをトリップするわけではありません。そのような変更の多くは構造体のサイズを変更するため、すべてではありませんが、キャッチされます。これに対する解決策はわかりません。

あるいは、ライブラリ自体に決して含まれない特別な公開ヘッダーがある場合は、おそらく (サポートしているコンパイラに対するテストの対象となります...) 公開プロトタイプを 1 つの型と内部のもので書くことができます。もう一方と。ただし、ライブラリがパブリックに面したThing構造体を何らかの方法で認識できるようにヘッダーを構造化して、そのサイズを確認できるようにすることをお勧めします。

于 2010-12-14T18:25:54.873 に答える
0

これは古い質問ですが、それも私を噛んでいるので、ここで可能な答えを提供したいと思いました(私が使用しています)。

だからここに例があります:

// file.h
typedef struct { size_t space[3]; } publicType;
int doSomething(publicType* object);

// file.c
typedef struct { unsigned var1; int var2; size_t var3; } privateType;

int doSomething(publicType* object)
{
    privateType* obPtr  = (privateType*) object;
    (...)
}

利点: publicTypeスタックに割り当てることができます。

適切な配置を確保するために、正しい基になる型を選択する必要があることに注意してください (つまり、 を使用しないでくださいchar)。にも注意してくださいsizeof(publicType) >= sizeof(privateType)。この条件が常にチェックされるようにするには、静的アサートをお勧めします。最後に、構造が後で進化する可能性があると思われる場合は、パブリック型を少し大きくして、ABI を壊さずに将来の拡張の余地を残しておくことを躊躇しないでください。

短所: public 型から private 型へのキャストにより、厳密なエイリアシング警告が発生する可能性があります。

私は後で、この方法がstruct sockaddrBSD ソケット内と類似していることを発見しました。これは、厳密なエイリアシング警告で基本的に同じ問題を解決します。

于 2015-06-25T05:01:25.147 に答える
0

malloc() を使用できないと言う理由が少しわかりません。明らかに、組み込みシステムではメモリが限られているため、通常の解決策は、大きなメモリ プールを malloc し、必要に応じてそのチャンクを割り当てる独自のメモリ マネージャを用意することです。私はこれまで、このアイデアのさまざまな実装を見てきました。

ただし、あなたの質問に答えるために、module.c でそれらの固定サイズの配列を静的に割り当てて、「使用中」フラグを追加し、create_handle() で最初の空き要素へのポインターを返すようにしないのはなぜですか。

このアイデアの拡張として、「ハンドル」は実際のポインターではなく整数インデックスにすることができます。これにより、ユーザーがオブジェクトの独自の定義にキャストして悪用しようとする可能性を回避できます。

于 2010-12-14T15:24:19.660 に答える