タイプ・パンニング・テーマのバリエーション:インプレース・トリビアル・コンストラクション


9

これはかなり一般的なテーマであることは知っていますが、典型的なUBを見つけるのは簡単ですが、今のところこの亜種は見つかりませんでした。

そこで、実際のデータのコピーを避けながら、Pixelオブジェクトを正式に紹介しようとしています。

これは有効ですか?

struct Pixel {
    uint8_t red;
    uint8_t green;
    uint8_t blue;
    uint8_t alpha;
};

static_assert(std::is_trivial_v<Pixel>);

Pixel* promote(std::byte* data, std::size_t count)
{
    Pixel * const result = reinterpret_cast<Pixel*>(data);
    while (count-- > 0) {
        new (data) Pixel{
            std::to_integer<uint8_t>(data[0]),
            std::to_integer<uint8_t>(data[1]),
            std::to_integer<uint8_t>(data[2]),
            std::to_integer<uint8_t>(data[3])
        };
        data += sizeof(Pixel);
    }
    return result; // throw in a std::launder? I believe it is not mandatory here.
}

予想される使用パターン、大幅に簡略化:

std::byte * buffer = getSomeImageData();
auto pixels = promote(buffer, 800*600);
// manipulate pixel data

すなわち:

  • このコードには明確に定義された動作がありますか?
  • はいの場合、返されたポインタを使用しても安全ですか?
  • はいの場合、他のどのPixelタイプに拡張できますか?(is_trivial制限を緩和?3つのコンポーネントのみを持つピクセル?)

clangとgccの両方がループ全体を無に最適化します。これが私が望んでいることです。さて、これがいくつかのC ++ルールに違反しているかどうか知りたいのですが。

ゴッドボルトリンクをいじってみよう。

(注:std::byte質問はを使用しているため、にもかかわらずc ++ 17にタグを付けませんでしたchar


2
しかし、Pixel新しく配置された連続するは、まだの配列ではありませんPixel
Jarod42

1
@spectrasそれでも配列にはなりません。あなただけの隣同士のピクセルオブジェクトの束を持っています。それは配列とは異なります。
NathanOliver

1
それで、あなたはどこに何をしますpixels[some_index]*(pixels + something)?それはUBです。
NathanOliver

1
関連するセクションがここにあり、重要なフレーズは、Pが配列オブジェクトxの配列要素iを指す場合です。ここでpixels(P)は配列オブジェクトへのポインターではなく、単一のへのポインターPixelです。つまり、pixels[0]合法的にしかアクセスできません。
NathanOliver

3
wg21.link/P0593を読みたい。
ecatmur

回答:


3

結果をpromote配列として使用することは未定義の動作です。私たちが見れば[expr.add] /4.2我々は持っています

そうでなければ、もしP配列要素を指しi配列オブジェクトのx持つn要素([dcl.array])式P + JJ + P(ここでJの値を有するj)(おそらく、仮想的な)配列要素にポイント i+jx場合0≤i+j≤nと表現P - Jへポイント(おそらく、仮想的な)アレイ素子 i−jx場合0≤i−j≤n

実際には配列オブジェクトを指すポインターが必要であることがわかります。ただし、実際には配列オブジェクトはありません。あなたはPixelたまたまPixels、隣接するメモリに他の人をフォローしているシングルへのポインタを持っています。つまり、実際にアクセスできる唯一の要素は最初の要素です。他のものにアクセスしようとすると、ポインターの有効なドメインの終わりを過ぎているため、未定義の動作になります。


すぐにお調べいただきありがとうございます。代わりに、イテレータを作成します。余談ですが、これはそれ&somevector[0] + 1がUBであることも意味します(つまり、結果のポインターの使用はそうなります)。
1

@spectrasそれは実際に大丈夫です。オブジェクトを通過するポインタはいつでも取得できます。たとえそこに有効なオブジェクトがあったとしても、そのポインタを逆参照することはできません。
NathanOliver

はい、わかりやすくするためにコメントを編集しました。結果のポインタを逆参照することを意味していました:)確認していただきありがとうございます。
1

@spectras問題ありません。C ++のこの部分は非常に難しい場合があります。ハードウェアは私たちがやりたいことを実行しますが、それは実際にはコーディングの目的ではありません。私たちはC ++抽象マシンにコーディングしていて、それはパースニッキーマシンです;)うまくいけば、P0593が採用され、これがはるかに簡単になるでしょう。
NathanOliver

1
@spectrasいいえ。stdベクトルは配列を含むものとして定義されており、配列要素間でポインター演算を実行できます。残念ながら、UBに実行せずにC ++自体にstdベクトルを実装する方法はありません。
Yakk-Adam Nevraumont

1

返されたポインタの制限された使用に関する回答はすでにありstd::launderますが、最初のものにもアクセスできるようにする必要があると思うことも付け加えておきますPixel

これreinterpret_castは、Pixelオブジェクトが作成される前に行われます(で作成しない場合getSomeImageData)。したがってreinterpret_cast、ポインター値は変更されません。結果のポインタはstd::byte、関数に渡された配列の最初の要素を指します。

あなたが作成するとPixel、オブジェクトを、彼らがしようとしているネストされた範囲内std::byteの配列とstd::byte配列がされるストレージを提供するためPixelのオブジェクト。

ストレージを再利用すると、古いオブジェクトへのポインターが自動的に新しいオブジェクトを指す場合があります。しかし、これはここで起こっていることではないのでresultstd::byteオブジェクトではなくオブジェクトを指しPixelます。Pixelオブジェクトを指しているかのように使用すると、技術的には未定義の動作になります。

オブジェクトをreinterpret_cast作成した後でを実行してもPixelPixelオブジェクトとそのstd::byteストレージを提供するはポインター相互変換できないため、これはまだ成り立つと思います。その場合でも、ポインタはオブジェクトstd::byteではなくを指していPixelます。

Placement-Newの1つの結果から戻るポインターを取得した場合、その特定のPixelオブジェクトへのアクセスに関する限り、すべてが正常です。


また、std::byteポインタが適切に配置されPixelていること、および配列が本当に十分に大きいことを確認する必要があります。私が覚えている限り、標準でPixelは、と同じ配置であるstd::byteか、またはパディングがないことを実際に要求していません。


また、これはPixel些細なことや実際には他のプロパティに依存していません。std::byte配列が十分なサイズであり、Pixelオブジェクトに対して適切に配置されている限り、すべてが同じように動作します。


私はそれが正しいと信じています。配列の事(のunimplementabilityがあってもstd::vector)問題ではなかった、あなたはまだに必要があると思いstd::launderplacement-のいずれかにアクセスする前に結果newPixel秒。現時点でstd::launderは、これはUBです。これは、隣接するPixelsが、ロンダーされたポインターから到達できるためです。
1

@Fureeish 戻る前にstd::launder適用された場合、なぜUBになるのかわかりませんresult。隣接Pixelは、eel.is / c++draft / ptr.launder#4についての私の理解により、洗濯されたポインタを介して「到達可能」ではありません。また、元の配列全体に元のポインターから到達できるため、それがUBであることがわかりませんでした。std::byte
クルミ

しかし、次Pixelstd::byteポインタから到達できませんが、launderedポインタから到達できます。これはここでは関係があると思います。でも修正されてうれしいです。
1

@Fureeish私が言うことができることから、与えられた例のどれもここでは適用されず、要件の定義も標準と同じであると述べています。到達可能性は、オブジェクトではなく、ストレージのバイト数で定義されます。バイトは、次によって占有さPixelの要素への元のポインタがあるため、元のポインタから私に到達可能と思われるstd::byteバイトを含む配列が用のストレージを構成するPixel「製造又は直接囲むアレイ内そのうちZはelement "条件が適用されます(ZY、つまりstd::byte要素自体)。
クルミ

Pixelただし、ポイントされたPixelオブジェクトは配列オブジェクトの要素ではなく、他の関連オブジェクトとのポインター相互変換もできないため、次の占有ストレージは、ロンダーポインターを介して到達できないと思います。しかし、私はまたstd::launder、その詳細についてこの詳細について初めて考えています。これについても100%確信が持てません。
クルミ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.