8
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

C++Advent Calendar 2020

Day 5

C++警察のあんたがたにpointer interconvertibleという新たなおもちゃのおしらせです

Last updated at Posted at 2020-12-06

C++ Advent Calender 2020

この記事はC++ Advent Calender 20205日目の記事です。

昨日は @yohhoy 氏のC++20便利機能の紹介:自動joinスレッドと停止機構 std::jthread, stop_tokenでした
明日は @MusicScience37 氏のstd::tuple の型を実行時に選択したいです

はじめに

この記事を書いている私は実のところpointer interconvertibleについてさほど詳しく有りません。多分@k_satoda 氏あたりに聞くほうがこの記事よりはるかにわかりやすく正確な回答が帰ってくるであろうことは火を見るより明らかですが、それでもまあ、自分の勉強も兼ねて筆をすすめることにします。

C++警察御用達のおもちゃ: Strict Aliasing rule

C++警察の皆さんならばつぎのコードが未定義動作であることがわかるはずです。

class A { int foo; };
class B { int foo; };

A a;
B* b = reinterpret_cast<B*>(&a);
b->foo = 3;

詳しくは
(翻訳)C/C++のStrict Aliasingを理解する または - どうして#$@##@^%コンパイラは僕がしたい事をさせてくれないの! - yohhoyの日記
に丸投げします。余談ですがC++20でStrict Aliasing ruleの定義が随分とすっきりしました。
[basic.lval] 11

上の例で言うなら、変数bに実際にアクセスが行われるb->foo = 3;のとき、baへのエイリアスにならないため、未定義動作となるのでした。

ではb->foo = 3;がなかったらどうでしょうか?実際にアクセスが行われないのでキャストだけではStrict Aliasing ruleに反しません。

じゃあ未定義動作ではないのか?いいえ、実はこのreinterpret_castはpointer interconvertibleではないのです。

pointer interconvertible

pointer interconvertibleの定義

規格書の定義を翻訳してみましょう。
[basic.compound] 4

2つのオブジェクトa, bがpointer interconvertibleであるのは次のいずれかのときである

  • 同一のオブジェクトである
  • 片方が共用体オブジェクトで、他方がその共用体の非静的メンバ変数である
  • 片方がstandard layoutクラスオブジェクトであり、他方は次のいずれかである場合
    • if (そのオブジェクトが非静的メンバ変数を持つ):
      →そのオブジェクトの最初の非静的メンバ変数
    • else:
      →基底クラスオブジェクト
  • 次のようなオブジェクトcが存在する: acがpointer interconvertibleでかつbcがpointer interconvertibleである

pointer interconvertibleの登場場面: reinterpret_cast

まずreinterpret_castがオブジェクトへのポインタをcv T*に変換するとき、次のように振る舞います。([expr.reinterpret.cast] 7)

static_­cast<cv T*>(static_­cast<cv void*>(v))

次に、cv1 void*cv2 T*static_castで変換するときを考えます。この変換ができるにはcv修飾子cv1, cv2を比べた時に同じかcv1よりもより修飾されている必要があります(例えばconststatic_castで外せない、それはそう)([expr.static.cast] 13)。

最後にcv1 void*cv2 T*static_castで変換して得たポインタの値が変換後の型として有効かを考えます。その条件は次のとおりです([expr.static.cast] 13)。

  1. 元となったポインタの値がメモリー上のAというアドレスを指していて、Aが変換後の型Tのアラインメント要求を満たす
    満たさない→得られたポインタの値はunspecified
  2. 元となったポインタの値がaというオブジェクトを指していてかつ、cv修飾子を無視した時にaとpointer interconvertibleなT型のオブジェクトbが存在する
    満たさない→ポインタの値が変化しない→cv2 T*型のポインタとしては有効ではない1

同じアドレスに2つのオブジェクトがあることとpointer interconvertibleであることは別である

struct A {
    int aaa;
    double bbb;
};
double foo(int* a)
{
    reinterpret_cast<const A*>(a)->bbb;
}
int main()
{
    A a{3, 4.2};
    std::cout << foo(a.aaa) << std::endl;
}

pointer interconvertibleの定義から、クラスAへのポインタとクラスAの非静的メンバ変数であるaaaへのポインタは変換可能です。

このとき、この2つは同じアドレスを指していることから「なるほど、pointer interconvertibleってのはつまり同じアドレスだったら変換できるってことか!」と考える人がいるかも知れません。

#include <cassert>
int main(){
    int arr[4];
    void* ptr1 = static_cast<void*>(arr);// OK
    void* ptr2 = static_cast<void*>(&arr);// OK
    assert(ptr1 == ptr2); // => true, same adress
    auto ptr1_2 = static_cast<int (*)[4]>(ptr2);// NG: not pointer interconvertible
    auto ptr2_2 = static_cast<int*>(ptr1);// NG: not pointer interconvertible
}

しかし上の例を見てください。変数ptr1_2/ptr2_2を初期化しようとしているcastはpointer interconvertibleではありません。言い換えると、配列オブジェクトと配列の先頭要素は同じアドレスにありますが、pointer interconvertibleではありません。

配列だけが例外と考えるのではなく、むしろpointer interconvertibleという関係性そのものが歴史的背景などからくる特殊例と考えるべきでしょう。

経緯

pointer interconvertibleのどこが新しいねん!というツッコミが来そうなのでこの記事を書くに至った経緯を書いておくことにします。

先日Qiitaに宣伝記事が出ていたC++入門サイトがあります
https://rinatz.github.io/cpp-book/
このサイトの査読をするうちに、キャストの説明に改善点がみつかり、ついでにStrict Aliasing Ruleの解説を書き加えました。
https://github.com/rinatz/cpp-book/pull/67
しかしその過程でStrict Aliasing Ruleの解釈が不安になりteratailとTwitterで質問をしました。
C++ - strict alias rule違反となるのはキャストした時点か|teratail

解釈についてあっていることが確認できてホッとしたところにC++有識者の @gnaggnoyil 氏からこんな指摘がありました。

pointer interconvertibleって何???

私にとって未知な用語に触れた私は調査を開始するのでした。

ちょうどほぼ同時並行して @yohhoy 氏がcpprefjpにis_pointer_interconvertible_base_ofについての項目を執筆されているのに励まされながら、遅刻しつつもこの記事は執筆されました。
https://github.com/cpprefjp/site/commit/373db2562dd489679aa96001078a6cb320052725

追記: is_pointer_interconvertible_base_ofの存在意義が問われる事態

https://github.com/cpprefjp/site/issues/824
でその存在意義に疑問符がつき
https://lists.isocpp.org/std-discussion/2020/12/0921.php
std-discussionに質問が飛んで
https://lists.isocpp.org/std-discussion/2020/12/0922.php
p0466の作者にメンションが飛んでる模様です

参考資料

  1. [expr.static.cast] 13には変換によってポインタの値が変化しないとしか書かれてないけど、多分もっと規格書をたどれば有効じゃないってことになる気がする、調べるには時間が足りなかった。

8
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?