9

さまざまな 3D 数学コードベースで、次のようなものに遭遇することがあります。

struct vec {
    float x, y, z;

    float& operator[](std::size_t i)
    {
        assert(i < 3);
        return (&x)[i];
    }
};

これは、実装が同じタイプであってもメンバー間にパディングを誤って追加することが許可されているため、違法です。ただし、実際にはそうするものはありません。

sを介して制約を課すことにより、これを合法にすることはできますstatic_assertか?

static_assert(sizeof(vec) == sizeof(float) * 3);

static_assertつまり、トリガーされていないということは、実行時に期待されることを意味しoperator[]、UB を呼び出さないことを意味しますか?

4

5 に答える 5

6

いいえ、整数をポインターに追加する場合、次が適用されるため ([expr.add]/5)、これは正しくありません。

ポインターオペランドと結果の両方が同じ配列オブジェクトの要素を指している場合、または配列オブジェクトの最後の要素の 1 つ後ろを指している場合、評価はオーバーフローを生成しません。それ以外の場合、動作は未定義です。

yx(1 つの要素を持つ配列と見なされる)の末尾の 1 つ後ろのメモリ位置を占有するため、 に 1 を追加すること&xは定義されていますが、 に 2 を追加することは定義されていません&x

于 2017-01-01T21:32:36.357 に答える
2

これが機能することを確信することはできません

通常のフロート配置プロパティと寛容なポインター演算のおかげで、これが実際には頻繁に完全に機能する場合でも、後続のメンバーの連続性は保証されません。

これは、C++ 標準の次の条項に規定されています。

[class.mem]/18 : 同じアクセス制御を持つ非静的データ メンバー (...) が割り当てられ、後のメンバーがクラス オブジェクト内でより高いアドレスを持つようになります。実装のアライメント要件により、隣接する 2 つのメンバーが互いの後に割り当てられない場合があります。

これを合法的な使用static_assertalignas制約にする方法はありません。できることは、各オブジェクトのアドレスが一意であるというプロパティを使用して、要素が連続していない場合にコンパイルを防止することだけです。

    static_assert (&y==&x+1 && &z==&y+1, "PADDING in vector"); 

ただし、オペレーターを再実装して、標準に準拠させることができます

operator[]安全な代替手段は、3 つのメンバーの隣接要件を取り除くため に再実装することです。

struct vec {
    float x,y,z; 

    float& operator[](size_t i)
    {
        assert(i<3); 
        if (i==0)     // optimizing compiler will make this as efficient as your original code
            return x; 
        else if (i==1) 
            return y; 
        else return z;
    }
};

最適化コンパイラは、再実装と元のバージョンの両方で非常によく似たコードを生成することに注意してください (こちらの例を参照してください)。したがって、準拠したバージョンを選択してください。

于 2017-01-01T21:40:51.473 に答える
2

型エイリアシング (本質的に同じデータに対して複数の型を使用すること) は、C++ では大きな問題です。メンバー関数を構造体から外して POD として維持すれば、うまくいくはずです。しかし

  static_assert(sizeof(vec) == sizeof(float) * 3);

あるタイプを別のタイプとして技術的に合法的にアクセスすることはできません。もちろん実際にはパディングはありませんが、C++ は vec が float の配列であり、vec の配列が 3 の倍数になるように制約された float の配列であり、キャスト &vecasarray[0 ] を vec * に使用することはできますが、 &vecasarray[1] をキャストすることはできません。

于 2017-01-01T22:24:14.313 に答える
2

標準によると、配列の外部でポインター演算を行うか、構造体と配列の内容をエイリアスするため、明らかに未定義の動作です。

問題は、math3D コードが集中的に使用される可能性があり、低レベルの最適化が理にかなっているということです。C++ 準拠の方法は、配列を直接格納し、アクセサーまたは配列の個々のメンバーへの参照を使用することです。そして、これらの 2 つのオプションはどちらも完全に問題ありません。

  • アクセサー:

    struct vec {
    private:
        float arr[3];
    public:
        float& operator[](std::size_t i)
        {
            assert(i < 3);
            return arr[i];
        }
        float& x() & { return arr[0];}
        float& y() & { return arr[1];}
        float& z() & { return arr[2];}
    };
    

    問題は、関数を左辺値として使用することは、古い C プログラマーにとって自然ではないということですv.x() = 1.0;。これは確かに正しいのですが、それを書くことを強制するようなライブラリは避けたいと思います。もちろん、セッターを使用することもできますが、可能であれば、一般的なイディオムのため、v.x = 1.0;よりも書くことを好みます。あくまで個人的な意見ですが、やよりは綺麗だと思います。v.setx(1.0);v.x = v.z = 1.0; v.y = 2.0;v.x() = v.z() = 1.0; v.y() = 2.0;v.setx(v.sety(1.0))); v.setz(2.0);

  • 参考文献

    struct vec {
    private:
        float arr[3];
    public:
        float& operator[](std::size_t i)
        {
            assert(i < 3);
            return arr[i];
        }
        float& x;
        float& y;
        float& z;
        vec(): x(arr[0]), y(arr[1]), z(arr[2]) {}
    };
    

    良い!v.xとを書くことができますがv[0]、どちらも同じメモリを表しています...残念ながら、コンパイラはまだ、参照が in struct 配列の単なるエイリアスであり、構造体のサイズが配列のサイズの 2 倍であることを確認するほど賢くありません!

これらの理由から、不正なエイリアシングが依然として一般的に使用されています...

于 2017-01-02T12:41:42.117 に答える
-3

データ メンバを配列として格納し、名前でアクセスするのはどうですか?

struct vec {
    float p[3];

    float& x() { return p[0]; }
    float& y() { return p[1]; }
    float& z() { return p[2]; }

    float& operator[](std::size_t i)
    {
        assert(i < 3);
        return p[i];
    }
};

編集:元のアプローチでは、x、y、および z がすべてメンバー変数である場合、構造体は常に 3 つの float のサイズになるため、制限されたサイズ内でアクセスstatic_assertするチェックに使用できます。operator[]

参照: C++ 構造体メンバーのメモリ割り当て

編集2:ブライアンが別の答えで言ったように、(&x)[i]それ自体は標準では未定義の動作です。ただし、3 つの float が唯一のデータ メンバーであることを考えると、このコンテキストのコードは安全なはずです。

構文の正確さについては、次のように説明します。

struct vec {
  float x, y, z;
  float* const p = &x;

  float& operator[](std::size_t i) {
    assert(i < 3);
    return p[i];
  }
};

これにより、ポインターのサイズだけ各 vec が増加します。

于 2017-01-01T21:29:02.693 に答える