1

タイプのユーザーがスタック上などでタイプしてインスタンス化できるようにしながら、不透明なタイプを作成したい

struct Foo obj;
/*...*/
Foo_init(&obj);
Foo_do_something(&obj, param);
/*...*/

慣習的な不透明ポインターのアプローチでは、この例の最初の行は許可されません。回避策として、公開されているが不透明な「データ」配列を公開ヘッダー ファイル内に固定サイズで配置しています。これはいくつかの例ではうまくいくようですが、次の 2 つの点については少し確信が持てません。

  1. Foo_get_bar と Foo_set_bar で行われているように、データ配列のアドレスをキャストしても安全ですか? これは私のテストでは問題なく動作しますが、疑わしいようです。

  2. FOO_DATA_SIZE が固定されたままである場合、ユーザー コードで ABI の互換性を期待するのは合理的ですか?


main.c (ユーザーコードの例)

#include <stdio.h>
#include <limits.h>
#include "foo.h"

int main() {
    struct Foo foo;
    Foo_init(&foo);
    Foo_set_bar(&foo, INT_MAX);
    int bar = Foo_get_bar(&foo);
    printf("Got bar: %d\n", bar);
}

foo.h (公開ヘッダー)

#pragma once
#include <inttypes.h>
#define FOO_DATA_SIZE (64)

struct Foo {
    uint8_t data[FOO_DATA_SIZE];
};

void Foo_init(struct Foo *f);
void Foo_set_bar(struct Foo *f, int barval);
int Foo_get_bar(struct Foo *f); 

foo.c (実装)

#include "foo.h"
#include <stdio.h>
#include <string.h>
#include <inttypes.h>

typedef int64_t bar_t;

struct Foo_private {
    bar_t bar;
};

_Static_assert(sizeof(struct Foo_private) <= FOO_DATA_SIZE,
    "FOO_DATA_SIZE is insufficient for struct Foo_private");

void Foo_init(struct Foo *foo) {
    struct Foo_private foodata;
    foodata.bar = (bar_t)0;
    memcpy(foo->data, &foodata, sizeof(struct Foo_private));
}

void Foo_set_bar(struct Foo *foo, int barval) {
    struct Foo_private *foodata = (void*)&(foo->data);
    foodata->bar = (bar_t)barval;
    int stored = (int)foodata->bar;
    if (stored != barval) {
        fprintf(stderr, "Foo_set_bar(%"PRId64"): warning: bar rounded to %"PRId64"\n", 
            (int64_t)barval, (int64_t)stored);
    }
}

int Foo_get_bar(struct Foo *foo) {
    struct Foo_private *foodata = (void*)&(foo->data);
    bar_t bar = foodata->bar;
    return (int)bar;
}
4

1 に答える 1

4

これらの投稿の情報を確認しました。

構造体の実装をそのように隠してはいけないのはなぜですか?

不透明なデータ型の静的割り当て

コメントと同様に。私はうまくいく答えを持っていると思います:私は不透明なポインター型の使用に切り替えましたが、ユーザーがスペースを割り当てるために alloca や malloc などを呼び出すことができるように、その大きさをユーザーに伝える関数呼び出しを公開しています。基本的には、割り当ては実装ではなくユーザーが行う必要があります。

変更されたヘッダー:

#pragma once
#include <inttypes.h>

struct Foo;
typedef struct Foo Foo;

void Foo_init(Foo *f);
void Foo_set_bar(Foo *f, int barval);
int Foo_get_bar(Foo *f); 
size_t Foo_data_size(void);

#define Foo_alloca() alloca(Foo_data_size())
#define Foo_new()  malloc(Foo_data_size())
#define Foo_delete(PTR) do { \
    free(PTR); \
    PTR = NULL; \
    } while(0)

変更された実装定義:

typedef int64_t bar_t;

struct Foo {
    volatile bar_t bar;
};

void Foo_init(struct Foo *foo) {
    struct Foo foodata;
    foodata.bar = (bar_t)0;
    memcpy(foo, &foodata, Foo_data_size());
}

size_t Foo_data_size() {
    return sizeof(struct Foo);
}

//...

次に、ユーザー コードで、 Foo_data_size() によって提供されるサイズで alloca を使用するか、便利なマクロを使用します。

このアプローチにより、固定サイズの制限が取り除かれ、前述のアライメントの問題が解決されることが期待されます。

無関係なのは、private struct 宣言の volatile という単語です。このように宣言せずに、少なくともwin32のgccは、互換性のない表現で特定の値を格納する際の制約チェックを最適化しようとしました。

使用例:

#include "foo.h"

//...

{
    Foo *foo = Foo_alloca();
    Foo_init(foo);
    Foo_set_bar(foo, INT_MAX);
    int bar = Foo_get_bar(foo);
    printf("Got bar: %d\n", bar);
}
于 2013-07-14T20:40:00.930 に答える