c++ - 配列 - c言語 関数 戻り値 文字列



C++でゼロサイズの割り当てに対して一意のアドレスを返すことの背後にある理論的根拠は何ですか? (2)

C ++でゼロサイズの割り当てに対して一意のアドレスを返すことの背後にある理論的根拠は何ですか?

背景:C11規格はmalloc(7.20.3メモリ管理機能)について述べています。

要求されたスペースのサイズがゼロの場合、動作は実装で定義されます。nullポインタが返されるか、または返されるポインタがオブジェクトへのアクセスに使用されない点を除いて、サイズがゼロ以外の値であるかのように動作します。

つまり、私が見ているように、 malloc常にゼロサイズの割り当てに成功します。ゼロサイズ割り当てのポインタでできることは、 freeような他のメモリ割り当て関数を呼び出すことだけです。

  • mallocNULL返す場合、 free(NULL)は問題ないため、これは成功と見なすことができます。
  • それが他の値を返すなら、それはまた成功です(それはNULLではないので)、唯一の条件はその値のfreeもまたうまくいくはずであるということです。

また、C11(これも7.20.3)は、mallocから返されるアドレスが一意でなければならないことを指定していません。それらは、互いに素なメモリ領域を指している必要があるだけです。

割り当てが成功した場合に返されるポインタは、任意のタイプのオブジェクトへのポインタに割り当てられ、割り当てられたスペース内のそのようなオブジェクトまたはそのようなオブジェクトの配列にアクセスするために使用されます。 。 割り当てられたオブジェクトの存続期間は、割り当てから割り当て解除までです。 そのような各割り当ては、他のどのオブジェクトからも外れたオブジェクトへのポインタを生成します。

ゼロサイズのすべてのオブジェクトは互いに素なAFAICTであり、それはmallocが複数のゼロサイズの割り当てに対して同じポインタを返すことができる(例: NULLが良い)、毎回異なるポインタ、あるいはいくつかの同じポインタなどを返すことを意味しNULL

それからC ++ 98は2つの生のメモリ割り当て関数と共に来ました:

void* operator new(std::size_t size);
void* operator new(std::size_t size, std::align_val_t alignment);

これらの関数は生のメモリを返すだけであることに注意してください:それらはどんなタイプAFAICTのどんなオブジェクトも作成も初期化もしません。

あなたはそれらをこのように呼ぶ:

#include <iostream>
#include <new>
int main() {
    void* ptr = operator new(std::size_t{0});
    std::cout << ptr << std::endl;
    operator delete(ptr, std::size_t{0});
    return 0;
}

C ++ 17標準の[new.delete.single]セクションはそれらを説明していますが、私が見ると重要な保証は[basic.stc.dynamic.allocation]ます。

要求されたスペースのサイズがゼロであっても、要求は失敗する可能性があります。 要求が成功した場合、その値p1が後でオペレーターdeleteに渡されない限り、戻される値は、前に戻された値p1とは異なる非ヌル・ポインター値(7.11)p0となります。 さらに、21.6.2.1および21.6.2.2のライブラリ割り当て関数では、p0は、呼び出し元からアクセス可能な他のオブジェクトのストレージとは別のストレージブロックのアドレスを表します。 サイズ0の要求として返されたポインタを介した間接化の影響は未定義です。

つまり、成功時に常に異なるポインタを返す必要があります。 これはmallocからの少しの変更です。

私の質問は次のとおりです。この変更の根拠は何ですか? (つまり、C ++でゼロサイズの割り当てに固有のアドレスを返すのは遅れています)

理想的には、答えは単なる代替案を探求し、それらのセマンティクスを動機付ける論文(または他の何らかの情報源)へのリンクになるでしょう。 通常、これらのC ++ 98の質問については、「C ++の設計と進化」を参照してくださいが、セクション10(メモリ管理)には何も記載されていません。 そうでなければ、ある種の権威ある参照がいいでしょう。

免責事項:私はredditでそれ求めましたが、私は十分にうまく尋ねなかったので、私は有用な答えを得ませんでした。 仮説しかないのであれば、答えとして自由に投稿してください。それは仮説にすぎないと述べてください。

また、redditでは、標準を変更するという提案があるかどうかなど、サイズがゼロの型についても続けました。この質問は、サイズがゼロに等しいときのrawメモリ割り当て関数のセマンティクスについてです。 サイズがゼロのタイプなどのトピックが回答に関連する場合は、それらを含めてください。 しかし、接線の問題に惑わされすぎないようにしてください。

また、redditには「具体的な目的のために」というような議論も投げかけられました。 私は答えの中で「最適化のため」よりも何か具体的なことを期待したいと思います。 たとえば、ある編集者がエイリアシングの最適化について言及していましたが、どのようなエイリアシングの最適化が参照解除できないポインタに適用されるのかと疑問に思い、それを誰にもコメントさせることができませんでした。 そのため、最適化について言及するつもりなら、それを示す小さな例が議論を豊かにするでしょう。


Answer #1

その理由は、単にコードが境界条件の特別な処理を必要としないためです。 私が最も言えることは、多くの場合、アルゴリズムはサイズがゼロのオブジェクトを境界条件として扱う必要があるということです。 あまり一般的でないのは、ポインタがオブジェクトと同じオブジェクトであるかどうかを調べるためにオブジェクトとポインタを比較するアルゴリズムですが、これはサイズがゼロのオブジェクトでも機能するはずです。

しかし、あなたの質問はこれが変化であると仮定しています。 私が知っているすべてのCおよびC ++の実装が1980年代後半に一時的に中断されたことを除けば、いつもこのように振舞いました。

dmrによるオリジナルのCコンパイラはこのように振る舞いました、しかし1987年頃のドラフトC標準はゼロサイズのオブジェクトのmallocがNULLを返すように指定しました。 これは本当に奇妙で、最終的なC-89標準でさえもそれを実装定義としていましたが、私はこの恐ろしいことをした実装に出会ったことは一度もありません。

私は私のブログの 「Malloc Madness」セクションでこれについてもっと話します。


Answer #2

問題は、C ++のオブジェクトは (サイズに関係なく) 一意のIDを持たなければならないということです。 そのため、大きさが異なっても、共存するオブジェクトは異なるアドレスを持つ必要があります 。これは、等しいと比較される2つのポインタが同じオブジェクトを指すと見なされるためです。

サイズが0のオブジェクトが同じアドレスを持つことができると認めた場合、2つのアドレスが同じオブジェクトであるかどうかは区別できなくなります。

"newはオブジェクトを返さない"問題についての多くのコメント。

この文脈でOOP用語を忘れてください。

C ++仕様には、「オブジェクト」という用語の意味が正確に定義されています。

CPPリファレンス:オブジェクト

特に:

C ++プログラムは、オブジェクトを作成、破棄、参照、アクセス、および操作します。 オブジェクトは、C ++では、次のものを持つストレージの領域です。

  • サイズ(sizeofで決定できます)。
  • 整列要件(alignofで決定できます)
  • ストレージ期間(自動、静的、動的、スレッドローカル)
  • 寿命(保存期間または一時的な期間によって制限される)
  • タイプ;
  • value(デフォルトで初期化されている非クラス型など、不定です)
  • オプションで、名前。

次のエンティティはオブジェクトではありません。値、参照、関数、列挙子、型、非静的クラスメンバ、ビットフィールド、テンプレート、クラスまたは関数テンプレートの特殊化、名前空間、パラメータパック、およびこれ。

変数は、宣言によって導入される、非静的データメンバーではないオブジェクトまたは参照です。

オブジェクトは、共用体のアクティブメンバを変更するとき、および一時的なオブジェクトが必要とされるときに、定義、new-expressions、throw-expressionsによって作成されます。





new-operator