14

私は動的型付け言語を書いています。現在、私のオブジェクトは次のように表されています。

struct Class { struct Class* class; struct Object* (*get)(struct Object*,struct Object*); };
struct Integer { struct Class* class; int value; };
struct Object { struct Class* class; };
struct String { struct Class* class; size_t length; char* characters; };

目標は、すべてを として渡し、属性struct Object*を比較してオブジェクトのタイプを発見できるようにすることです。classたとえば、使用するために整数をキャストするには、次のようにします (integerタイプが であると仮定しますstruct Class*)。

struct Object* foo = bar();

// increment foo
if(foo->class == integer)
    ((struct Integer*)foo)->value++;
else
    handleTypeError();

問題は、私が知る限り、C 標準では構造体の格納方法が保証されていないことです。私のプラットフォームでは、これは機能します。しかし、別のプラットフォームでは、前にstruct String保存し、上記でアクセスしたときに、実際にはにアクセスしている可能性があり、これは明らかに悪いことです。ここでの移植性は大きな目標です。valueclassfoo->classfoo->value

このアプローチの代替手段があります。

struct Object
{
    struct Class* class;
    union Value
    {
        struct Class c;
        int i;
        struct String s;
    } value;
};

ここでの問題は、共用体に格納できる最大のもののサイズと同じくらいのスペースを共用体が使用することです。一部の型が他の型の何倍も大きいことを考えると、これは、小さな型 ( int) が大きな型 ( ) と同じくらいのスペースを占有することを意味し、mapこれは受け入れがたいトレードオフです。

struct Object
{
    struct Class* class;
    void* value;
};

これにより、動作が遅くなるレベルのリダイレクトが作成されます。ここでは速度が目標です。

最後の選択肢は、 s を渡しvoid*、構造体の内部を自分で管理することです。たとえば、上記の型テストを実装するには:

void* foo = bar();

// increment foo
if(*((struct Class*) foo) == integer)
    (*((int*)(foo + sizeof(struct Class*))))++;
else
    handleTypeError();

これにより、私が望むすべて (移植性、異なるタイプの異なるサイズなど) が得られますが、少なくとも 2 つの欠点があります。

  1. 恐ろしい、エラーが発生しやすい C. 上記のコードは、単一メンバーのオフセットのみを計算します。整数よりも複雑な型ではさらに悪化します。マクロを使えば少しは軽減できるかもしれませんが、これはどう考えても痛いです。
  2. オブジェクトを表すものがないためstruct、スタック割り当てのオプションはありません (少なくとも、ヒープに独自のスタックを実装する必要はありません)。

基本的に、私の質問は、どうすればお金を払わずに欲しいものを手に入れることができるでしょうか? 移植性があり、タイプごとにサイズが異なり、リダイレクトを使用せず、コードをきれいに保つ方法はありますか?

編集: これは、SO の質問に対して私が今まで受け取った中で最高の応答です。答えを選ぶのは難しかった。SO では 1 つの回答しか選択できないため、解決策につながる回答を選択しましたが、皆さんは賛成票を受け取りました。

4

6 に答える 6

7

Python が標準 C を使用してこの問題を解決する方法については、 Python PEP 3123 ( http://www.python.org/dev/peps/pep-3123/ ) を参照してください。Python ソリューションは、問題に直接適用できます。基本的に、これを行いたい:

struct Object { struct Class* class; };
struct Integer { struct Object object; int value; };
struct String { struct Object object; size_t length; char* characters; };

オブジェクトが整数であることがわかっている場合は、およびInteger*に安全にキャストできます。Object*Object*Integer*

于 2009-09-28T07:18:12.030 に答える
7

C は、最初のアプローチが機能することを十分に保証します。行う必要がある唯一の変更は、ポインターのエイリアシングを正常にするために、キャストするunionすべての を含むスコープ内にある必要があることです。struct

union allow_aliasing {
    struct Class class;
    struct Object object;
    struct Integer integer;
    struct String string;
};

(ユニオンを何かに使用する必要はありません-スコープ内にある必要があります)

標準の関連部分は次のとおりだと思います。

[#5] 1 つの例外を除いて、ユニオン オブジェクトのメンバーの値が、オブジェクトへの最新のストアが別のメンバーに対して行われたときに使用される場合、動作は実装定義です。共用体の使用を簡素化するために、1 つの特別な保証が行われます。共用体に、共通の初期シーケンス (以下を参照) を共有する複数の構造体が含まれている場合、および共用体オブジェクトに現在これらの構造体の 1 つが含まれている場合、共通の構造体を検査することが許可されます。それらのいずれかの最初の部分は、共用体の完全な型の宣言が表示される場所であればどこでも使用できます。対応するメンバーが 1 つ以上の初期メンバーのシーケンスに対して互換性のある型 (およびビット フィールドの場合は同じ幅) を持っている場合、2 つの構造体は共通の初期シーケンスを共有します。

(これは直接問題ないとは言いませんが、2 つstructの s が共通の初期シーケンスを持ち、一緒に結合された場合、同じようにメモリに配置されることを保証すると信じています - それは確かに慣用的でしたとにかく、これを想定するのに長い間 C を使用してください)。

于 2009-09-28T05:13:32.077 に答える
3

ISO 9899:1999 (C99 標準) のセクション 6.2.5 には、次のように記載されています。

構造体型は、順番に割り当てられたメンバー オブジェクト (および、特定の状況では不完全な配列) の空でないセットを記述します。各オブジェクトには、オプションで指定された名前と、場合によっては異なる型があります。

セクション 6.7.2.1 には次のようにも書かれています。

6.2.5 で説明したように、構造体はメンバーのシーケンスから構成される型であり、その記憶域は順序付けられたシーケンスで割り当てられ、共用体は記憶域がオーバーラップするメンバーのシーケンスから構成される型です。

[...]

構造体オブジェクト内で、非ビット フィールド メンバーとビット フィールドが存在するユニットには、宣言された順序で増加するアドレスがあります。適切に変換された構造体オブジェクトへのポインターは、その最初のメンバー (または、そのメンバーがビットフィールドの場合は、それが存在するユニット) を指し、その逆も同様です。構造体オブジェクト内に名前のないパディングがある場合がありますが、先頭にはありません。

これにより、必要なものが保証されます。

あなたが言う質問で:

問題は、私が知る限り、C 標準では構造体の格納方法が保証されていないことです。私のプラットフォームでは、これは機能します。

これはすべてのプラットフォームで機能します。これはまた、最初の選択肢 (現在使用しているもの) が十分に安全であることを意味します。

しかし、別のプラットフォームでは、struct String Integer はクラスの前に値を格納する可能性があり、上記で foo->class にアクセスすると、実際には foo->value にアクセスすることになり、これは明らかに悪いことです。ここでの移植性は大きな目標です。

これを行うことは、準拠したコンパイラでは許可されていません。[最初の宣言セットを参照していると仮定して、String を Integer に置き換えました。よく調べてみると、共用体が埋め込まれた構造体を参照していた可能性があります。classコンパイラは、 と の並べ替えをまだ許可されていませんvalue]

于 2009-09-28T06:10:14.383 に答える
2

問題は、私が知る限り、C 標準では構造体の格納方法が保証されていないことです。私のプラットフォームでは、これは機能します。しかし、別のプラットフォームでは、前にstruct String保存し、上記でアクセスしたときに、実際にはにアクセスしている可能性があり、これは明らかに悪いことです。ここでの移植性は大きな目標です。valueclassfoo->classfoo->value

私はあなたがここで間違っていると信じています。まず、メンバーstruct Stringがいないためです。第二に、Cは構造体のメンバーのメモリ内のレイアウトを保証するvalueと信じているためです。そのため、次のサイズが異なります。

struct {
    short a;
    char  b;
    char  c;
}

struct {
    char  a;
    short b;
    char  c;
}

C が保証を行わない場合、コンパイラはおそらくそれらの両方を同じサイズになるように最適化します。ただし、構造体の内部レイアウトが保証されるため、自然な配置規則が適用され、2 番目の構造体が最初の構造体よりも大きくなります。

于 2009-09-28T05:16:22.943 に答える
2

この質問と回答によって提起されたペダンティックな問題に感謝しますが、CPython が同様のトリックを「多かれ少なかれ永遠に」使用しており、多種多様な C コンパイラで何十年も機能していることに言及したかっただけです。具体的には、object.h、 のようなマクロ、 のようなPyObject_HEAD構造体を参照してください。すべての種類の Python オブジェクト (C API レベルで下) は、それらへのポインターを取得しており、害を及ぼすことなくPyObject、前後にキャストされます。最後に ISO C 標準で海上弁護士を演じてからしばらく経ちましたがPyObject*、手元にコピーがありません (!) 。 20年近く...

于 2009-09-28T05:27:18.617 に答える