質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
ポインタ

ポインタはアドレスを用いてメモリに格納された値を"参照する"変数です。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

Q&A

解決済

5回答

1500閲覧

C++: lifetime が終了した object を指すポインタに対する操作について

nus_miz

総合スコア5

ポインタ

ポインタはアドレスを用いてメモリに格納された値を"参照する"変数です。

C++

C++はC言語をもとにしてつくられた最もよく使われるマルチパラダイムプログラミング言語の1つです。オブジェクト指向、ジェネリック、命令型など広く対応しており、多目的に使用されています。

2グッド

3クリップ

投稿2020/03/30 10:48

編集2020/04/17 04:23

※ 本質問文は用語の使用法が正確ではありません。
話の流れが分かり辛くならないようにあえて質問時のままとしております。
正確な用法に関しては yohhoy さんのご回答を参照してください。

前提・実現したいこと

C++17、及び C++20(予定)の規格に関する質問です。

lifetime が終了し、かつ storage が他のオブジェクトによって再利用されたオブジェクトを指しているポインタに対して、規格上どのような操作が許されるのか気になり、調べていました。
しかし規格のドラフトから適当な記述を見つける事ができず、困っています。

もしご存じの方がいらっしゃいましたらご助言をいただけないでしょうか。

C++17 (N4659)、及び C++20 (N4849) のドラフトを参照しています。

発生している問題

https://timsong-cpp.github.io/cppwp/n4659/basic.life#6
おそらく上記のあたりが該当の記述だと思ったのですが、上記の段落では以下のように述べられておりました。

Before the lifetime of an object has started but after the storage which the object will occupy has been allocated or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released,

すなわち、「lifetime の終了後、その storage が解放または再利用されていないオブジェクト」を指すポインタに関する記述となっています。
「lifetime が終了し、かつ storage が再利用されているオブジェクト」を指すポインタについての記述は見つかりませんでした。
しかし上記の段落に付随する Example は、「lifetime が終了し、かつ storage が再利用されているオブジェクト」を指すポインタに関する例となっているように読めました。
以下は N4659 からの引用です

C++

1#include <cstdlib> 2 3struct B { 4 virtual void f(); 5 void mutate(); 6 virtual ~B(); 7}; 8 9struct D1 : B { void f(); }; 10struct D2 : B { void f(); }; 11 12void B::mutate() { 13 new (this) D2; // reuses storage — ends the lifetime of *this 14 f(); // undefined behavior 15 ... = this; // OK, this points to valid memory 16} 17 18void g() { 19 void* p = std::malloc(sizeof(D1) + sizeof(D2)); 20 B* pb = new (p) D1; 21 pb->mutate(); 22 *pb; // OK: pb points to valid memory 23 void* q = pb; // OK: pb points to valid memory 24 pb->f(); // undefined behavior, lifetime of *pb has ended 25}

g() における pb や、mutate() における this は「lifetime が終了し、かつ storage が再利用されているオブジェクト」を指すポインタになっているように思われます。

この Example がこの段落に存在することで混乱してしまいました。
私が読み間違えているだけでこの段落の記述は「lifetime が終了し、かつ storage が再利用されているオブジェクト」を指すポインタにも適用されるのでしょうか。

ソースコードの例

例えば、以下のような操作がそれぞれ Undefined Behavior となるのか、ならないのかが気になっています。

C++

1#include <cstdint> 2#include <new> 3 4int main() 5{ 6 std::uint32_t* p32 = new std::uint32_t[4]{}; 7 std::uint16_t* p16 = new (p32) std::uint16_t[4]{}; 8 *p32; // (a) 9 std::uint32_t lval = *p32 + 100; // (b) 10 std::uint32_t* p32_2 = p32 + 2; // (c) 11 *p32_2 = 0; // (d) 12 p32 = new (p32) std::uint32_t[4]; // (e) 13 delete [] p32; // (f) 14}

追記:
https://lists.isocpp.org/std-discussion/2020/04/0530.php
こちらのメールで非常に丁寧にご回答いただきました。簡単に要約させていただきます。
[basic.life]/6 は段落を通して以下のポインタについて語られています。
"any pointer that represents the address of the storage location where the object will be or was located"
([basic.life]/6 中の such a pointer は上記を指します)
私の誤解の原因は、
"Before the lifetime of an object has started but after the storage which the object will occupy has been allocated41 or, after the lifetime of an object has ended and before the storage which the object occupied is reused or released"
という条件が、[basic.life]/6 中で言及されるポインタ全体を修飾するものだと考えていたことでした。
実際には、上記の条件は一文目と二文目のみに関係するものとのことです。
(三文目は上記条件に当てはまらないものについての言及となります)

私の誤解にお付き合いいただきありがとうございました。
ベストアンサーについて、私の読み間違いを指摘してくださった yohhoy さんと非常に迷いましたが、問題解決のメールを出すきっかけとなった alphya さんとさせていただきました。

yumetodo, alphya👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hoshi-takanori

2020/03/30 19:42

確かに、規格がバグってる気がしますね。reuse と release を同列に語ってますが、reuse は object の lifetime が終了するだけで storage が無効になるわけではないので、ポインタ自体は有効なはず。
nus_miz

2020/04/01 14:17

規格バグで、正しい記述が存在しないとすると厄介ですね… 軽く DR も探してみましたが、本件に関するものはまだ見つけられておりません。 http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1027 このあたりで、似たような問題意識はあったようなのですが、ちょっとずれていそうですね。
guest

回答5

0

ベストアンサー

[4/10 17:18 yohhoy さんの回答より、用語の使い方を修正しました]

lifetime が終了し、かつ storage が他のオブジェクトによって再利用されたオブジェクトを指しているポインタに対して、規格上どのような操作が許されるのか

基本的には、[basic.life]/8 に対応する記述があるようです。生存期間 - cppreference.com による日本語の説明は次の通りです:

別のオブジェクトが占めていたアドレスに新しいオブジェクトが作成される場合、元のオブジェクトのすべてのポインタ、参照、名前は自動的に新しいオブジェクトを参照するようになり、新しいオブジェクトの生存期間が始まれば、それらを新しいオブジェクトを操作するために使用することができますが、これは以下の条件が満たされる場合に限ります。

  • 新しいオブジェクトのための記憶域が、元にオブジェクトが占めていた記憶域の位置と、正確にオーバーラップしている。
    • 新しいオブジェクトが元のオブジェクトと同じ型である (トップレベルの cv 修飾は無視します)。
    • 元のオブジェクトの型が const 修飾されていない。
    • 元のオブジェクトがクラス型の場合は、 const 修飾された型または参照型の非静的データメンバを含まない。
    • 元のオブジェクトが T 型の最も派生したオブジェクトであり、新しいオブジェクトが T 型の最も派生したオブジェクトである (つまり、それらが基底クラス部分オブジェクトでない)。

上記の条件を満たさない場合でも、ポインタ最適化バリア std::launder を適用することによって、新しいオブジェクトへの有効なポインタを取得できます。

 

私が読み間違えているだけでこの段落の記述は「lifetime が終了し、かつ storage が再利用されているオブジェクト」を指すポインタにも適用されるのでしょうか。

この質問に関連した質問 c++ - reusing object's space by another object - Stack Overflow の回答では、[basic.life]/6 の "Otherwise" 節の記述:

... Otherwise, such a pointer refers to allocated storage ([basic.stc.dynamic.deallocation]), and using the pointer as if the pointer were of type void*, is well-defined.

が「あるオブジェクトの生存期間(lifetime)が終了したあとに、同オブジェクトが占めていたストレージ上にオブジェクトが生成されている、その同ストレージへのポインタ」にも適用されるように読み取れます。

ただし、"Otherwise" 節の次の "Indirection through such a..." からの文は、生存期間 - cppreference.com の対応する箇所に「オブジェクトの生存期間が開始する前かつオブジェクトが占める記憶域が確保された後、またはオブジェクトの生存期間が終了した後かつオブジェクトが占めていた記憶域が再利用または解放される前」と指定があるので適用されないように思えます。また、上記 cppreference の記事では "Otherwise" 節や [basic.life]/6 に付随する Example については言及されていませんでした。

(個人的には [basic.life]/6 は「あるオブジェクトの生存期間(lifetime)が終了したあとに、同オブジェクト#1が占めていたストレージ上にオブジェクトが生成されていない、その同ストレージへのポインタ」に関する記述となるようにも読めると思います...)

例えば、以下のような操作がそれぞれ Undefined Behavior となるのか、ならないのか

ここでは、上記の StackOverflow の回答の解釈([basic.life]/6 の "Otherwise" 節の記述が「あるオブジェクトの生存期間(lifetime)が終了したあとに、同オブジェクトが占めていたストレージ上にオブジェクトが生成されている、その同ストレージへのポインタ」にも適用される)に従って回答します。

(a), (b): この場合、p32の型がvoid*型であるかのようにポインターを使用することができますが、void*型変数の間接参照はできないため Undefined Behavior
(c): p32の型をvoid*と仮定したとき、キャストなしに代入することはできないので Undefined Behavior
(d): (a), (b) と同じ理由により Undefined Behavior
(e), (f): [basic.life]/8 の条件を満たすので適格


[追記]

nus_miz さんのご指摘のように、[basic.life]/6 の記述は一見すると「あるオブジェクトの生存期間(lifetime)が終了したあとに、同オブジェクトが占めていたストレージ上にオブジェクトが生成されていない、その同ストレージへのポインタ」に関する記述だと読めるが、"Indirection through such a..." からの文を含めた "Otherwise" 節以降の文は「あるオブジェクトの生存期間(lifetime)が終了したあとに、同オブジェクトが占めていたストレージ上にオブジェクトが生成されている、その同ストレージへのポインタ」にも適用される、という解釈だと、付随する Example がこの位置にあることや、*pb の間接参照が適格だということに説明が付きそうです。

"Indirection" からの文が適用されないという解釈の根拠は cppreference の記事でしたが、このサイトも C++ の公式のものだというわけではないので、上記のような規格の記述に一貫性のある解釈の方が妥当だと思います。

また、このような解釈では、例示された (c), (d), (e), (f) の操作は、未定義動作となる条件である [basic.life]/6.1 から 6.5 に該当しないためどれも適格な操作だと考えられます。ただし、(a), (b) は strict aliasing rule によって未定義動作だと考えられます。


[追記 2]

ISO C++ Standard - Discussion へこの質問を送り、回答をいただきました:
https://lists.isocpp.org/std-discussion/2020/04/0511.php

この回答によれば、[basic.life]/6 の記述は「あるオブジェクトの生存期間(lifetime)が終了したあとに、同オブジェクトが占めていたストレージ上にオブジェクトが生成されている、その同ストレージへのポインタ」にも適用されるようです。

また、例示された操作については次のようになるようです。

(a): UB ではありません。なぜなら:
[basic.lval]/1.4 "lvalue は、xvalue でない glvalue です"
[basic.life]/6 "そのようなポインターを介した間接指定は許可されますが、結果の lvalue は、以下で説明するように、限られた方法でのみ使用できます"
[basic.life]/7 "その値に依存しない glvalue のプロパティの使用は well-defined です"

(b): 式 *p32 + 100 は、「その値に依存する glvalue のプロパティを使用している」ため UB

(c): p32 は有効なポインタであるため、これは問題ありません

(d): ~~ [basic.life]/7.1 より、「オブジェクトにアクセスするために glvalue が使用される」ため、UB ~~ lifetime が終了したオブジェクトへのアクセスではないため、UB ではありません(コメント欄参照)

(e): この時点で、タイプ std::uint16_t の 4 つのオブジェクトは、ストレージの再利用のために lifetime が終了しています。ただし、std::uint32_t タイプの 4 つのオブジェクトは、 {} がないために作成されていません

(f): これにより、ストレージの割り当てが解除されます。std::uint32_t は no non-trivial destructor をもつため UB ではありません

投稿2020/04/02 07:54

編集2020/04/12 09:17
alphya

総合スコア124

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nus_miz

2020/04/03 10:02 編集

ありがとうございます。 Stack Overflow の質問は把握しておりませんでした。 大変参考になります。 ただ、storage が再利用されたオブジェクトを指すポインタに関して "Otherwise" 節が適用されるが、 "Indirection through such a..." の部分が適用されない、という解釈には違和感があります。 [basic.life]/6 の Example には以下の記述があります。 *pb; // OK: pb points to valid memory この時、pb が指しているオブジェクトは(D2 型のオブジェクトによって) storage が再利用されています。 この例は [basic.life]/8 には当たらないため、pb は古いオブジェクトを指し続けているはずです。 (実際、pb->f(); は undefined behavior になるとされています) もし、pb を void* としてしか扱えないのならば、 *bp は undefined behavior になるはずではないでしょうか。
alphya

2020/04/03 10:32

> pb を void* としてしか扱えないのならば、 *bp は undefined behavior になるはず 確かにそうですね... この説明は何も思いつきません、申し訳ないです... (本当に規格バグなのかもしれません...)
nus_miz

2020/04/12 08:11

> ISO C++ Standard - Discussion へこの質問を送り、回答をいただきました: まことにありがとうございます。お手数をおかけしました。 いくつか疑問点は残りますが、自分でもメールを出すなどして解決を図りたいと思います。 一応、残った疑問点をこちらに書き残します。 > 「あるオブジェクトの生存期間(lifetime)が終了したあとに、同オブジェクトが占めていたストレージ上にオブジェクトが生成されている、その同ストレージへのポインタ」にも適用されるようです。 このあたりの根拠がいまいち不明瞭に感じました。根拠を示さずとも記述から自明に読み取ることができる、ということなのでしょうか? > (d): [basic.life]/7.1 より、「オブジェクトにアクセスするために glvalue が使用される」ため、UB > 原文: UB because, quoting [basic.life]/7.1, "the glvalue is used to access the object". p32_2 が表現するアドレスに存在する uint32_t のオブジェクトは lifetime が終了していないと考えているのですが、それでも UB になるのでしょうか (uint16_t 4 つ分のオブジェクトの作成により、uint32_t 4 つ分のオブジェクトのうち、前半二つだけの lifetime が終了したと考えております)
alphya

2020/04/12 09:48 編集

> このあたりの根拠がいまいち不明瞭に感じました。根拠を示さずとも記述から自明に読み取ることができる、ということなのでしょうか? そのつもりで回答しました。例えば、例示されたコード (a) の操作について、「あるオブジェクトの生存期間(lifetime)が終了したあとに、同オブジェクトが占めていたストレージ上にオブジェクトが生成されている、その同ストレージへのポインタ」である p32 の間接参照が UB でない理由として、[basic.life]/6 の記述 "Indirection through such a pointer is permitted but the resulting lvalue may only be used in limited ways, as described below." が挙げられていることから読み取れます。 > p32_2 が表現するアドレスに存在する uint32_t のオブジェクトは lifetime が終了していないと考えているのですが、それでも UB になるのでしょうか(uint16_t 4 つ分のオブジェクトの作成により、uint32_t 4 つ分のオブジェクトのうち、前半二つだけの lifetime が終了したと考えております) 確かにそうですね! lifetime を終了させる方法は [basic.life]/5 で > A program may end the lifetime of any object by reusing the storage which the object occupies or by explicitly calling the destructor for an object of a class type with a non-trivial destructor. と説明されていますが、(d) の場合ストレージの再利用もデストラクタの呼び出しも行われていない(できない)ので、lifetime は終了していないと考えられます。ですので、これは UB にならないでしょう。
nus_miz

2020/04/12 09:22

すみません。私の書き込みが言葉足らずで誤解を与えてしまったかもしれません。 > このあたりの根拠がいまいち不明瞭に感じました。根拠を示さずとも記述から自明に読み取ることができる、ということなのでしょうか? こちらは元のメールに対する書き込みでした。 元のメールの筆者は「適用される」と主張していることは読み取れました。 ただ、筆者が主張の根拠としている規格の記述が不明瞭であると感じました。
alphya

2020/04/12 10:15 編集

> こちらは元のメールに対する書き込みでした。 なるほど、そういうことでしたか...! > ただ、筆者が主張の根拠としている規格の記述が不明瞭であると感じました。 あくまで個人的な意見なのですが、これは規格の解釈の問題なのだと思います。ですので、他の箇所にこの話題に関する記述があるというわけではないと思います。また、標準の ML は規格の作成に関わる方も見ていると思うので、[basic.life]/6 の記述はこの解釈で問題ないと思います。
alphya

2020/04/12 10:15

ですが、「規格のこの箇所はわかりにくいので、このように書いたらどうか?」のように提案してみるのもいいかもしれませんね!
nus_miz

2020/04/17 04:21

便乗する形になって申し訳ございませんが、私からもメールを出させていただき、疑問が解決しました。 https://lists.isocpp.org/std-discussion/2020/04/0530.php (質問本文も上記回答に基づき編集いたします) 誠にありがとうございました。
guest

0

lifetime が終了し、かつ storage が他のオブジェクトによって再利用されたオブジェクトを指しているポインタ

上記は用語の使い方が不正確であり、そのことが一連の混乱を招いているように思えます。

C++における オブジェクト(object) は:[intro.object]/p1

  • 定義(definition) または new式(new-expression) によって生成されます。
  • 生成(construction)から破壊(destruction)されるまでの 生存期間(lifetime) では、一定のストレージ(storage)を占有します。
  • 名前(name) を付けられます。("名無しのオブジェクト"もあります)

近い概念としては、「あるオブジェクト#1の生存期間(lifetime)が終了したあとに、同オブジェクト#1が占めていたストレージ上にオブジェクト#2が生成されている、その同ストレージへのポインタ」でしょうか。


[baisc.life]/p6 の第一センテンス構造は次の通りです。要約してしまうと「有効オブジェクトで占められていないストレージを指すポインタは、非常に限られた用途でのみ使うことができる」と言っています。

(条件)
"Before the lifetime of an object has started but after the storage which the object will occupy has been allocated"
or,
"after the lifetime of an object has ended and before the storage which the object occupied is reused or released,"

(主語)
any pointer that represents the address of the storage location where the object will be or was located
(述語)
may be used but only in limited ways.

後続センテンスはいずれも、上記ポインタを利用可能な条件について説明しています。

For an object under construction or destruction, [...]
Otherwise, such a pointer refers to allocated storage [...]
Indirection through such a pointer is permitted but [...]

Exampleの解釈としては:

C++

1void B::mutate() { 2 new (this) D2; // reuses storage — ends the lifetime of *this 3 f(); // undefined behavior 4 // this->f(); つまり非staticメンバ関数呼び出し 5 // 条件(6.2)に該当するためundefined behavior 6 ... = this; // OK, this points to valid memory 7 // ポインタ値 this の利用はOK(Otherwise~の文) 8} 9 10void g() { 11 void* p = std::malloc(sizeof(D1) + sizeof(D2)); 12 B* pb = new (p) D1; 13 pb->mutate(); 14 *pb; // OK: pb points to valid memory 15 // 条件(6.1)~(6.5)いずれも該当せずOK(Indirection~の文) 16 void* q = pb; // OK: pb points to valid memory 17 // ポインタ値 pb をvoid*と扱うのはOK(Otherwise~の文) 18 pb->f(); // undefined behavior, lifetime of *pb has ended 19 // pb->f(); は非staticメンバ関数呼び出し 20 // 条件(6.2)に該当するためundefined behavior 21}

投稿2020/04/10 08:03

編集2020/04/10 08:25
yohhoy

総合スコア6191

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nus_miz

2020/04/10 08:48 編集

本質問で対象としたかったのは、まさに「あるオブジェクト#1の生存期間(lifetime)が終了したあとに、同オブジェクト#1が占めていたストレージ上にオブジェクト#2が生成されている、その同ストレージへのポインタ」のことです。 用語の訂正と丁寧な解説、まことにありがとうございます。 「ポインタが指すオブジェクトとは無関係に、ポインタが表現するアドレスが有効オブジェクトで占められているかどうかが問題である」という解釈は非常に納得がいきました。 しかし、そうなると B::mutate() における this や、g() における pb は new (this) D2; の後、 「指している D1 型オブジェクトの lifetime は終了したが、それはそれとして D2型の有効なオブジェクトが占めるストレージのアドレスを表現する」ポインタ となるように思えます。 [basic.life]/6 は「有効オブジェクトで占められていないストレージを指すポインタ」に関する記述である、という解釈に従うと、結局 pb 等は [basic.life]/6 の対象外となってしまうのでしょうか。
yohhoy

2020/04/10 09:09 編集

んー、こじつけ感ありますが Example コメントは "OK, this points to valid memory" 、つまり後から構築されたD2型オブジェクトについては触れてないのですよね。 B::mutate() スコープの this、main() スコープの pb はあくまでも当初D1型オブジェクトを指すと解釈すればいいのでしょうかねぇ。 実際に、続く[basic.life]/p7では(ざっくり)「型が同じときにかぎってポインタは新オブジェクトをちゃんと指す」と定義しています。std::launder関数などはこのあたりの厄介な問題に対処するためにC++17に導入されています。
nus_miz

2020/04/10 09:28

ありがとうございます。 まさしく今回の疑問は launder を含む P0137 による影響について調べている最中、P1839 を読んでいる中で沸いてきたものです。 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0137r1.html http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1839r2.pdf P1839 を読んだ後、以下のように SomeType の内部表現にアクセスるるのは C++17 時点では合法なのだろうか、と疑問に思いました。 https://wandbox.org/permlink/FhpyaCIpLHPYIf5B > Example コメントは後から構築されたD2型オブジェクトについては触れてない 確かに、 D2 が作成直後に(デストラクタ呼び出し等によって)即 lifetime を終えたとすると、 Example について疑問点はなくなります。 しかし、だとするとやはり「別の新しい有効なオブジェクトが占めているストレージを表現するポインタ」に関する記述は[basic.life]/6 にはないということになってしまうのでしょうか。
yohhoy

2020/04/10 09:44

ポインタ(pointer)とは何ぞやという話なのかもしません。 C++におけるポインタは 位置情報(アドレス) と 型情報 の組ですから、オリジナルD1型のポインタは(何らかの特別な措置なしには)別のD2型のポインタとはそもそも無関係であるとみなせばよいのかも? # ということを定義している具体的なWordingまでは知らないのですが、意味合いとしてはそう間違ってないかなと。
guest

0

んー私にはドラフトを読む能力がないので、私の考えで書きます。

1.lifetimeが終了している → 終了していると言うことは、終了する以降の処理は基本的に終了前の状況とは違う(割り当てられたオブジェクトとの紐付けが切れている)のでまともに動くことが保証できない(未定義動作になってもしかたない)
2.Storageが再利用されている → lifetimeが終了する前に紐付けられたオブジェクトとの紐付けが切れ(他の用途に利用できるようになっている)たあと別のオブジェクトが上書きされている状態

つまりこの条件が揃ったら動作は未定義だよ。やらないでね。という風に示している以外の解釈は個人的には難しいかと思います。
lifetimeが終了している場合のコンパイル時の動作はおそらくコンパイラ依存のため私にはわかりません。

投稿2020/04/09 03:23

TrakRailySurely

総合スコア11

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nus_miz

2020/04/09 10:26

ありがとうございます。 もし [basic.life]/6 の Example が存在しなければ、TrakRailySurely さんがおっしゃるように「規格中で特に言及がないので未定義動作」という解釈も十分考えられたと思います。 しかし[basic.life]/6 の Example は、storage が再利用されたオブジェクトを指すポインタに関するもので、さらにそのポインタに対する一部の操作は合法なように書かれているように見えました。 にもかかわらず規格の文章中には storage 再利用後のポインタに関する記述が存在しないように思えたため、本質問を投稿させていただきました。
guest

0

江添さんの言及を引っ張っておきます。

https://twitter.com/EzoeRyou/status/1247936602451501056

ストレージが再利用される時に元のオブジェクトの寿命は一旦尽きているのだから原文は正しいのでは。

脳みそが回ってないのでこれを解釈するのは寝て起きてからにすることにします。


https://twitter.com/yumetodo/status/1248107858731749376?s=20
サンプルにある
*pb; // OK: pb points to valid memory
がなんでvalidなのかよくわからないです。なんでpb->f();がだめなのにデレファレンスしていいのか。

https://twitter.com/EzoeRyou/status/1248144850819502080?s=20
妥当なメモリを参照しているというだけでD1としての寿命は尽きているからでは?

https://twitter.com/EzoeRyou/status/1248145254345109505?s=20
アドレスは取得できるしそのアドレスをvoid *型にキャストすることもできるが、pbは実装側からみるとD1を指していたようにみえるので(mutate()呼び出しは不透明)、D1を指しているものとして最適化がされてしまうかもしれないから動作を保証できないということでは。

投稿2020/04/08 17:51

編集2020/04/09 08:32
yumetodo

総合スコア5850

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nus_miz

2020/04/09 09:51 編集

yumetodo さん、江添さん ありがとうございます。 > ストレージが再利用される時に元のオブジェクトの寿命は一旦尽きているのだから原文は正しいのでは。 はい。原文に矛盾があるとは考えておりません。 ストレージが再利用される場合、元のオブジェクトの寿命が尽きた後にストレージが再利用される、すなわち一旦 [basic.life]/6 の内容に該当する状態になることはその通りだと考えております。 一旦 [basic.life]/6 で言及される状態になるが、すぐに storage が再利用され、[basic.life]/6 の条件から外れてしまうように読めるため、疑問に思いました。 記述に矛盾があるというわけではなく、storage 再利用後の扱いに「触れていない」ため、どこかほかの所に記述があるのだろうか?だとしたら Example がこの場所にある意味は?などと考えておりました。 もしかすると、一旦 [basic.life]/6 で言及される状態になったのち、ストレージが再利用される([basic.life]/6 の条件から外れる)が、ストレージ再利用後のポインタの扱いは特に言及がないため、ポインタの状態は変化せず、[basic.life]/6 の内容が引き続き有効である、ということでしょうか。 > *pb がなぜ valid なのか storage が再利用された後も [basic.life]/6 の記述が有効なのだとすれば、こちらに関しては大きな疑問点はありません。 *pb が valid で、 pb->f(); が undefined なのは、 "Indirection through such a pointer is permitted but the resulting lvalue may only be used in limited ways, as described below." の内容に合致していると思われます。
yumetodo

2020/04/09 12:30

>こちらに関しては大きな疑問点はありません。 ああ、まあそこは自分の疑問点を投げて返ってきたのでついでに貼ってるだけなので気にせずに。
guest

0

f(); // undefined behavior

基底側にも継承側にも定義がないっぽいのに挙動未定義というのはちょっと意味が分からないです。
単なるポインターを他の変数に代入したときに元の変数の寿命が尽きるように言っておられるようですが、変数はスタック上にあり、実体はヒープ上にあるケースだと思います。
まずそこら辺のことを理解されるのが先ではないでしょうか?
void g()内で確保されたヒープ領域は、解放されていませんので寿命を迎えておりません。このままだと実行終了時にメモリーリークが発生します。

ソースコードの例

この世にないコーディング(書きゃ書ける)を挙げて「たら話」をしてもしょうがないと思います。

何かこちらが誤解しているならばいいんですが。

投稿2020/04/08 13:30

kendji

総合スコア92

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nus_miz

2020/04/08 14:24

本質問は C++ 規格の解釈についてのものです。 f(); // undefined behavior 質問の本文にも記載してある通り、上記を含むソースコードは C++規格書のドラフトからの引用です。 以下のリンクをご参照ください。 https://timsong-cpp.github.io/cppwp/n4659/basic.life#6 上記 Example においては、f(); の定義がたとえどんなものであったとしても f(); を呼び出した時点で undefined behavior となることを示していると考えられます。 (すなわち、f() がどのような処理であるかはここでの議論に無関係のため、省略されているのだと解釈しております。) > g()内で確保されたヒープ領域が開放されていない こちらも同様に、議論に無関係のために解放処理について省略されているのだと思われます。 おそらく kendji さんがおっしゃっている「変数の寿命」はいわゆる「変数のスコープ」を指していると思われます(私の思い違いでしたら申し訳ございません)。 https://timsong-cpp.github.io/cppwp/n4659/basic.scope 今回論点としている「寿命」は、規格で lifetime と表記されるものであり、 scope とは異なります。 https://timsong-cpp.github.io/cppwp/n4659/basic.life そして、object の lifetime は(変数の scope とは無関係に)storage が再利用されることで終了すると規格に明記されています。 https://timsong-cpp.github.io/cppwp/n4659/basic.life#1.4
kendji

2020/04/08 14:51

引用なんですね、なるほど。 では逆に話しやすいです。あれは意図的にビルドエラーになるようにしてあると思います。また、スマートポインターやらTaskクラスなどの標準化でクラッシュフリーな、本当にクラッシュフリーな時代になって、それであんな今風なキーワードをちりばめた化石のようなコードを見て腹わたが煮え繰り返ったのは事実です。 設計寿命、スコープ寿命、ヒープかデータ領域かスタックか、いろんな切り口がありますよね。スタック領域は関数スコープ内でスタックポインターが足されたら引かれたりするコードを見てきた人ならドアの開け閉め程度の感覚でわかっていると思います。 ヒープのハンドルは、どこで保管するかで紐付きを簡単に失いますが、解放するまでインスタンスは無くなりません。なので、「変数のスコープ」と、基本的に非同期に存在し、コード側で計画して管理しなければならないヒープ領域のインスタンス寿命の話をしました。 objectとstrageが規格上何を指すのか分かりませんが、関数スコープやクラススコープ内でインスタンス生成されるものはスコープに同期してインスタンスが生成/破棄され、newやmalloc、何らかの資源を非同期(動的)に確保した場合は、そのインスタンスは開放しない限り存在し続けます。
nus_miz

2020/04/08 15:15

> 解放するまでインスタンスはなくなりません ここでの「インスタンス」は規格で「object」と呼ばれるものだと思われます。 その場合、storage の解放はインスタンス(object)の lifetime を終了させる十分条件ですが、必要条件ではありません。 上でも述べたように、storage が解放されなくても、他のオブジェクトによって storage が再利用された場合も lifetime は終了します。 https://timsong-cpp.github.io/cppwp/n4659/basic.life#1.4 本質問で引用した Example はまさにこの例にあたります。 new (this) D2; の時点で、先に作られていた D1 型のインスタンスの lifetime が終了しています。 なおこの他に、非トリビアルなデストラクタ呼び出しによっても object の lifetime は終了します。 https://timsong-cpp.github.io/cppwp/n4659/basic.life#1.3
kendji

2020/04/08 15:31

> new (this) D2; ストレージの意味がわかりました。入れ物であってプログラミング的な言葉ではないみたいですね。 これをビルドして、関数テーブルやvテーブルの構成がD1になっているか、D2になっているか、どちらでもないのか。それが答えだと思います。事実から辿るのが自然であり高効率ですよね。 OSやBIOS、各種APIのバグを見つけては現象を特定し、回避してきた世代からすると、いくら電源供給してもデムパ供給しても微塵も動かない、止まらない(閾値などはっきりしない)ルールには頼らないのが正解なんです。
yumetodo

2020/04/08 15:37

ところが我々はそのルールたるC++規格書の解釈をしようとしているわけです。コンパイラが正しく規格書にそって実装しているとは限りませんからコンパイラは頼りになりません。故に規格書の読解は今回のように困難を極めるわけです。
nus_miz

2020/04/08 15:50 編集

kendji さん 規格における object や storage に関しては以下をご参照ください。 https://timsong-cpp.github.io/cppwp/n4659/intro.object yumetodo さん ありがとうございます。 > コンパイラが正しく規格書にそって実装しているとは限りませんから 一点だけ補足させていただくと、仮にコンパイラが規格書の内容を正しく実装していたとしても、コンパイル結果のみを確認した場合、未定義動作の結果としてそうなっていたり、処理系定義の結果としてそうなっていたりする可能性があり、その判断ができないのも問題になる思われます。
kendji

2020/04/08 16:12

規格書は大抵後追いなので、事実が先んじます。 MISRA-Cなどに代表されるコードベリファイヤーの設計あたりだと多少問題になると思いますが、基本的には事実に基づきます。 本件のコードは粗悪なもので、各所でコンテキストを破断する内容になっています。もうちょっと突っ込めば、多少でも美意識のある者なら目を背けたくなる記述です。 意識してそうしてあるかも知れません。 お互い罠に掛かったように思います。
yumetodo

2020/04/08 16:51

事実を疑うことから始まるのですよ。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問