ある特定のC++関数では、doubleの半分の数を格納するために一時的に使用したいfloatの大きなバッファーへのポインターがあります。このバッファをdoubleを格納するためのスクラッチスペースとして使用する方法はありますか?これは、標準で許可されています(つまり、未定義の動作ではありません)。
要約すると、私はこれが欲しいです:
void f(float* buffer)
{
double* d = reinterpret_cast<double*>(buffer);
// make use of d
d[i] = 1.;
// done using d as scratch, start filling the buffer
buffer[j] = 1.;
}
私が見る限り、これを行う簡単な方法はありません。正しく理解していれば、reinterpret_cast<double*>
このように型のエイリアシングが原因で未定義の動作が発生し、memcpy
またはfloat/double union
を使用するには、データをコピーして余分なスペースを割り当てる必要があります。これにより、目的が損なわれ、私の場合はコストがかかります(C++では型のパンニングにユニオンを使用することは許可されていません)。
フロートバッファは、doubleに使用するために正しく調整されていると見なすことができます。
次のコードはそれを行うための有効な方法だと思います(これは実際にはアイデアに関する小さな例にすぎません)。
#include <memory>
void f(float* buffer, std::size_t buffer_size_in_bytes)
{
double* d = new (buffer)double[buffer_size_in_bytes / sizeof(double)];
// we have started the lifetime of the doubles.
// "d" is a new pointer pointing to the first double object in the array.
// now you can use "d" as a double buffer for your calculations
// you are not allowed to access any object through the "buffer" pointer anymore since the floats are "destroyed"
d[0] = 1.;
// do some work here on/with the doubles...
// conceptually we need to destory the doubles here... but they are trivially destructable
// now we need to start the lifetime of the floats again
new (buffer) float[10];
// here we are unsure about wether we need to update the "buffer" pointer to
// the one returned by the placement new of the floats
// if it is nessessary, we could return the new float pointer or take the input pointer
// by reference and update it directly in the function
}
int main()
{
float* floats = new float[10];
f(floats, sizeof(float) * 10);
return 0;
}
Newの配置から受け取ったポインタのみを使用することが重要です。そして、フロートの後ろに新しいものを配置することが重要です。無操作構造であっても、フロートの寿命をやり直す必要があります。
のことを忘れます std::launder
およびreinterpret_cast
コメントで。新しい配置はあなたのために仕事をします。
編集:メインでバッファを作成するときは、適切に配置されていることを確認してください。
更新:
コメントで議論された事柄についての最新情報を提供したかっただけです。
これを行うには、a)参照によってfloatポインターを渡して更新するか、b)関数から新しく取得したfloatポインターを返すことができます。
a)
void f(float*& buffer, std::size_t buffer_size_in_bytes)
{
double* d = new (buffer)double[buffer_size_in_bytes / sizeof(double)];
// do some work here on/with the doubles...
buffer = new (buffer) float[10];
}
b)
float* f(float* buffer, std::size_t buffer_size_in_bytes)
{
/* same as inital example... */
return new (buffer) float[10];
}
int main()
{
float* floats = new float[10];
floats = f(floats, sizeof(float) * 10);
return 0;
}
次に言及するより重要なことは、placement-newはメモリオーバーヘッドを持つことが許可されているということです。したがって、実装では、返された配列の前にメタデータを配置できます。それが起こった場合、いくつのダブルが私たちの記憶に収まるかという素朴な計算は明らかに間違っています。問題は、実装が特定の呼び出しのために事前に取得するバイト数がわからないことです。ただし、残りのストレージに収まることがわかっているdoubleの量を調整する必要があります。ここ( https://stackoverflow.com/a/8721932/3783662 )は別のSO投稿で、ハワード・ヒナントがテストスニペットを提供しました。オンラインを使用してこれをテストしましたコンパイラーは、些細な破壊可能な型(doubleなど)の場合、オーバーヘッドが0であることを確認しました。より複雑な型(std :: stringなど)の場合、8バイトのオーバーヘッドがありました。ただし、これはプラットフォーム/コンパイラーによって異なる場合があります。ハワードのスニペットで事前にテストしてください。
ある種の配置newを使用する必要がある理由(new []または単一要素newのいずれかによる)の質問の場合:ポインターを任意の方法でキャストできます。しかし、最終的には、値にアクセスするときに、厳密なエイリアシングルールの違反を避けるために、適切なタイプを使用する必要があります。簡単に言えば、ポインタで指定された場所にポインタタイプのオブジェクトが実際に存在する場合にのみ、オブジェクトへのアクセスが許可されます。では、どのようにしてオブジェクトに命を吹き込みますか?標準は言う:
https://timsong-cpp.github.io/cppwp/intro.object#1 :
「オブジェクトは、定義によって、新しい式によって、ユニオンのアクティブメンバーを暗黙的に変更するとき、または一時オブジェクトが作成されるときに作成されます。」
興味深いと思われる追加のセクターがあります。
https://timsong-cpp.github.io/cppwp/basic.life#1 :
「オブジェクトがクラスまたは集約タイプであり、オブジェクトまたはそのサブオブジェクトの1つが、些細なデフォルトコンストラクター以外のコンストラクターによって初期化される場合、オブジェクトは非空の初期化を持っていると言われます。タイプTのオブジェクトの存続期間は、次の場合に始まります。
さて、ダブルスは些細なことなので、些細なオブジェクトを生き生きとさせ、実際の生きているオブジェクトを変更するために何らかの行動を取る必要があると主張するかもしれません。フロート用のストレージを最初に取得し、ダブルポインターを介してストレージにアクセスすると、厳密なエイリアシングに違反するため、「はい」と言います。したがって、実際の型が変更されたことをコンパイラに通知する必要があります。この最後のポイント3全体は、かなり物議を醸した議論でした。あなたはあなた自身の意見を形成するかもしれません。これですべての情報が手元にあります。
これは2つの方法で実現できます。
最初:
void set(float *buffer, size_t index, double value) {
memcpy(reinterpret_cast<char*>(buffer)+sizeof(double)*index, &value, sizeof(double));
}
double get(const float *buffer, size_t index) {
double v;
memcpy(&v, reinterpret_cast<const char*>(buffer)+sizeof(double)*index, sizeof(double));
return v;
}
void f(float *buffer) {
// here, use set and get functions
}
2番目:float *
の代わりに、「タイプレス」のchar[]
バッファを割り当て、新しい配置を使用してフロートまたはダブルを内部に配置する必要があります。
template <typename T>
void setType(char *buffer, size_t size) {
for (size_t i=0; i<size/sizeof(T); i++) {
new(buffer+i*sizeof(T)) T;
}
}
// use it like this: setType<float>(buffer, sizeOfBuffer);
次に、このアクセサーを使用します。
template <typename T>
T &get(char *buffer, size_t index) {
return *std::launder(reinterpret_cast<T *>(buffer+index*sizeof(T)));
}
// use it like this: get<float>(buffer, index) = 33.3f;
3番目の方法はphönの答えのようなものかもしれません(その答えの下の私のコメントを参照してください)、残念ながら私は この問題 のために適切な解決策を作ることができません。
これは、それほど怖くない別のアプローチです。
あなたは言う、
...フロート/ダブルユニオンは...余分なスペースを割り当てないと不可能です。これは目的を損ない、私の場合はコストがかかります...
したがって、各ユニオンオブジェクトに1つではなく2つのfloatを含めるだけです。
static_assert(sizeof(double) == sizeof(float)*2, "Assuming exactly two floats fit in a double.");
union double_or_floats
{
double d;
float f[2];
};
void f(double_or_floats* buffer)
{
// Use buffer of doubles as scratch space.
buffer[0].d = 1.0;
// Done with the scratch space. Start filling the buffer with floats.
buffer[0].f[0] = 1.0f;
buffer[0].f[1] = 2.0f;
}
もちろん、これによりインデックス作成がより複雑になり、呼び出しコードを変更する必要があります。しかし、オーバーヘッドはなく、より明らかに正しいです。
tl; drコマンドラインで実行することをコンパイラに指示しない限り、ポインタのエイリアスを作成しないでください。
これを行う最も簡単な方法は、どのコンパイラスイッチが厳密なエイリアシングを無効にしているかを把握し、問題のソースファイルに使用することです。
必要がありますね?
これについてもう少し考えました。新しい配置に関するすべてのことにもかかわらず、これが唯一の安全な方法です。
どうして?
同じアドレスを指す異なるタイプの2つのポインターがある場合は、そのアドレスにエイリアスを設定しているため、コンパイラーをだます可能性が高くなります。 そして、それらのポインタにどのように値を割り当てたかは関係ありません。コンパイラはそれを覚えていません。
したがって、これが唯一の安全な方法であり、それがstd::pun
が必要な理由です。
この問題は、ポータブルC++では解決できません。
ポインタのエイリアスに関しては、C++は厳密です。逆説的ですが、これにより、非常に多くのプラットフォームでコンパイルできます(たとえば、double
番号がfloat
番号とは異なる場所に格納されている場合など)。
言うまでもなく、移植可能なコードを求めているのであれば、持っているものを再コード化する必要があります。次善の策は、実用的であることです。これは、私が遭遇したすべてのデスクトップシステムで機能することを受け入れます。おそらくstatic_assert
コンパイラ名/アーキテクチャについて。