T *をレジスタに渡すことができるのに、unique_ptr <T>はできないのはなぜですか?


85

私はCppCon 2019でチャンドラー・カルースの講演を見ています:

ゼロコストの抽象化はありません

その中で、彼は、std::unique_ptr<int>over を使用することによって生じるオーバーヘッドの大きさに驚いた様子の例を示していint*ます。そのセグメントは、およそ17:25の時点で始まります。

彼のサンプルのスニペットのペア(godbolt.org)のコンパイル結果を確認できます。実際、コンパイラーがunique_ptr値を渡そうとはしていないようです。ただのアドレス-レジスタ内、ストレートメモリ内のみ。

Carruth氏が27:00頃に指摘する点の1つは、C ++ ABIが値渡しパラメーター(すべてではないが、一部ではない可能性があります。レジスター内ではなく。

私の質問:

  1. これは実際には一部のプラットフォームでのABI要件ですか?(どれですか?)または、特定のシナリオでの悲観化に過ぎないのでしょうか?
  2. なぜABIはそのようなのですか?つまり、構造体/クラスのフィールドがレジスター内、または単一のレジスター内に収まる場合、なぜそのレジスター内で渡すことができないのでしょうか?
  3. C ++標準委員会は、この点について近年、またはこれまでに議論しましたか?

PS-この質問をコードなしで残さないように:

単純なポインタ:

void bar(int* ptr) noexcept;
void baz(int* ptr) noexcept;

void foo(int* ptr) noexcept {
    if (*ptr > 42) {
        bar(ptr); 
        *ptr = 42; 
    }
    baz(ptr);
}

一意のポインタ:

using std::unique_ptr;
void bar(int* ptr) noexcept;
void baz(unique_ptr<int> ptr) noexcept;

void foo(unique_ptr<int> ptr) noexcept {
    if (*ptr > 42) { 
        bar(ptr.get());
        *ptr = 42; 
    }
    baz(std::move(ptr));
}

8
ABI要件が正確に何であるかはわかりません
ハロルド

6
推測しなければならない場合、それはthis有効な場所を指すポインターを必要とする自明ではないメンバー関数に関係していると思います。unique_ptrそれらがあります。そのためにレジスターをスピルすると、「レジスターの受け渡し」全体の最適化が無効になります。
StoryTeller-Unslander Monica

2
itanium-cxx-abi.github.io/cxx-abi/abi.html#calls。したがって、この動作が必要です。どうして?itanium-cxx-abi.github.io/cxx-abi/cxx-closed.html、問題C-7を検索します。そこにはいくつかの説明がありますが、あまり詳細ではありません。しかし、そうです、この動作は私には論理的に思えません。これらのオブジェクトは通常通りスタックを通過できます。それらをスタックにプッシュしてから、参照を渡すこと(「重要な」オブジェクトのみの場合)は無駄に見えます。
geza

6
ここではC ++が独自の原則に違反しているようですが、これは非常に悲しいことです。unique_ptrはコンパイル後に消えるだけだと140%確信していました。結局のところ、コンパイル時に認識されるのは、遅延デストラクタ呼び出しだけです。
1人の猿の分隊、

7
@MaximEgorushkin:手動で作成した場合は、ポインタをスタックではなくレジスタに配置します。
einpoklum

回答:


49
  1. これは実際にはABI要件ですか、それとも特定のシナリオにおける単なる悲観化ですか?

1つの例は、System VアプリケーションバイナリインターフェイスAMD64アーキテクチャプロセッササプリメントです。このABIは、64ビットx86互換CPU(Linux x86_64アーキテクチャー)用です。Solaris、Linux、FreeBSD、macOS、LinuxのWindowsサブシステムで使用されています。

C ++オブジェクトに重要なコピーコンストラクタまたは重要なデストラクタがある場合、オブジェクトは非表示の参照によって渡されます(オブジェクトは、クラスINTEGERを持つポインタによってパラメータリストで置き換えられます)。

重要なコピーコンストラクタまたは重要なデストラクタのいずれかを持つオブジェクトは、明確に定義されたアドレスを持つ必要があるため、値で渡すことはできません。関数からオブジェクトを返すときにも同様の問題が発生します。

自明なコピーコンストラクターと自明なデストラクタで1つのオブジェクトを渡すために使用できる汎用レジスターは2つだけであることに注意してください。つまりsizeof、16以下のオブジェクトの値のみがレジスターで渡されます。呼び出し規約の詳細な扱いについては、Agner Fogによる呼び出し規約、特に7.1オブジェクトの受け渡しと返却を参照してください。レジスターでSIMDタイプを渡すための別個の呼び出し規則があります。

他のCPUアーキテクチャには異なるABIがあります。


  1. なぜABIはそのようなのですか?つまり、構造体/クラスのフィールドがレジスター内、または単一のレジスター内に収まる場合、なぜそのレジスター内で渡すことができないのでしょうか?

これは実装の詳細ですが、例外が処理されるとき、スタックの巻き戻し中に、自動ストレージ期間が破棄されるオブジェクトは、その時点までにレジスターが破棄されているため、関数スタックフレームに対して相対的にアドレス可能でなければなりません。スタック巻き戻しコードは、デストラクタを呼び出すためにオブジェクトのアドレスを必要としますが、レジスター内のオブジェクトにはアドレスがありません。

体系的に、デストラクタはオブジェクトを操作します

オブジェクトは、その構築期間([class.cdtor])、存続期間、および破棄期間のストレージ領域を占有します。

また、オブジェクトのIDがそのアドレスであるため、アドレス可能なストレージが割り当てられていない場合、オブジェクトはC ++に存在できません。

単純なコピーコンストラクターがレジスターに保持されているオブジェクトのアドレスが必要な場合、コンパイラーはオブジェクトをメモリに格納してアドレスを取得するだけです。一方、コピーコンストラクターが重要な場合、コンパイラーはそれをメモリに格納するだけではなく、参照を取得するコピーコンストラクターを呼び出す必要があるため、レジスターにオブジェクトのアドレスを必要とします。呼び出し規約は、おそらくコピーコンストラクターが呼び出し先でインライン化されたかどうかに依存できません。

これについて考えるもう1つの方法は、自明にコピー可能な型の場合、コンパイラーがオブジェクトのをレジスターに転送し、そこから必要に応じてプレーン・メモリー・ストアによってオブジェクトをリカバリーできるということです。例えば:

void f(long*);
void g(long a) { f(&a); }

System Vを使用するx86_64では、ABIは次のようにコンパイルされます。

g(long):                             // Argument a is in rdi.
        push    rax                  // Align stack, faster sub rsp, 8.
        mov     qword ptr [rsp], rdi // Store the value of a in rdi into the stack to create an object.
        mov     rdi, rsp             // Load the address of the object on the stack into rdi.
        call    f(long*)             // Call f with the address in rdi.
        pop     rax                  // Faster add rsp, 8.
        ret                          // The destructor of the stack object is trivial, no code to emit.

彼の示唆に富む講演で、チャンドラー・カルース、物事を改善する可能性のある破壊的な動きを実装するために、(とりわけ)破壊的なABIの変更が必要になる可能性があると述べています。IMO、ABIの変更は、新しいABIを使用する関数が新しい異なるリンケージを持つことを明示的にオプトインする場合、たとえば、それらをextern "C++20" {}ブロックで(おそらく、既存のAPIを移行するための新しいインライン名前空間で)宣言する場合、非破壊的である可能性があります。そのため、新しいリンケージを持つ新しい関数宣言に対してコンパイルされたコードのみが新しいABIを使用できます。

呼び出された関数がインライン化されている場合、ABIは適用されないことに注意してください。リンク時のコード生成と同様に、コンパイラは他の翻訳単位で定義された関数をインライン化したり、カスタムの呼び出し規約を使用したりできます。


コメントは詳細な議論のためのものではありません。この会話はチャットに移動さました
Samuel Liew

8

一般的なABIでは、重要なデストラクタ->レジスタを渡すことができません

(コメントでの@haroldの例を使用した@MaximEgorushkinの回答のポイントのイラスト。@ Yakkのコメントに従って修正されました。)

コンパイルした場合:

struct Foo { int bar; };
Foo test(Foo byval) { return byval; }

あなたが得る:

test(Foo):
        mov     eax, edi
        ret

つまり、Fooオブジェクトはtestレジスタ(edi)に渡され、レジスタ()にも返されeaxます。

デストラクタが自明でない場合(std::unique_ptrOPの例のように)-一般的なABIはスタックに配置する必要があります。これは、デストラクタがオブジェクトのアドレスをまったく使用しない場合でも当てはまります。

したがって、何もしないデストラクタの極端な場合でも、コンパイルすると、次のようになります。

struct Foo2 {
    int bar;
    ~Foo2() {  }
};

Foo2 test(Foo2 byval) { return byval; }

あなたが得る:

test(Foo2):
        mov     edx, DWORD PTR [rsi]
        mov     rax, rdi
        mov     DWORD PTR [rdi], edx
        ret

無駄なローディングと保存で。


私はこの議論に納得していません。重要なデストラクタは、as-ifルールを禁止するために何もしません。アドレスが監視されない場合、アドレスが必要である理由はまったくありません。したがって、準拠するコンパイラーが喜んでそれをレジスターに入れることができる場合、そうすることで観測可能な動作が変更されない場合(そして、現在のコンパイラーは、呼び出し元がわかっている場合は実際に変更します)。
ComicSansMS

1
残念ながら、それは別の方法です(これの一部はすでに理由を超えていることに同意します)。正確に言うと、あなたが提供した理由がstd::unique_ptr、レジスタに電流を流すことを許可する考えられるABIを不適合にすることになると私は確信していません。
ComicSansMS

3
「自明なデストラクタ[引用が必要]」は明らかに誤りです。実際にアドレスに依存するコードがない場合、as-ifはアドレスが実際のマシンに存在する必要がないことを意味します。アドレスは抽象マシンに存在している必要がありますが、抽象マシンの中で実際のマシンに影響を与えないものは、削除が許可されているかのようです。
Yakk-Adam Nevraumont

2
@einpoklumレジスターが存在することを示す規格には何もありません。registerキーワードは、「アドレスを取得することはできません」とだけ述べています。標準に関する限り、抽象的なマシンしかありません。「あたかも」とは、実際のマシンの実装は、標準で定義されていない動作まで、抽象マシンが「あたかも」振る舞うだけでよいことを意味します。現在、レジスタにオブジェクトを置くことには非常に難しい問題があり、誰もが広範囲にわたって話し合っています。また、標準でも議論されていない呼び出し規約には、実用的なニーズがあります。
Yakk-Adam Nevraumont

1
@einpoklumいいえ、その抽象マシンでは、すべてのものにアドレスがあります。ただし、アドレスは特定の状況でのみ監視できます。このregisterキーワードは、物理マシンで「アドレスがない」ことを実際に困難にするものをブロックすることによって、物理マシンがレジスタに何かを保存することを簡単にすることを目的としていました。
Yakk-Adam Nevraumont

2

これは実際には一部のプラットフォームでのABI要件ですか?(どれですか?)または、特定のシナリオでの悲観化に過ぎないのでしょうか?

コンパイルユニットの境界で何かが表示されている場合、それが暗黙的に定義されていても明示的に定義されていても、ABIの一部になります。

なぜABIはそのようなのですか?

基本的な問題は、コールスタックを上下に移動すると、レジスタが常に保存および復元されることです。そのため、それらへの参照またはポインタを持つことは現実的ではありません。

インライン化とそれに起因する最適化は、それが発生したときに優れていますが、ABI設計者はそれが発生することに依存できません。最悪のケースを想定してABIを設計する必要があります。プログラマーが、最適化レベルに応じてABIが変更されたコンパイラーに満足することはないと思います。

論理コピー操作は2つの部分に分割できるため、簡単にコピーできる型をレジスタに渡すことができます。パラメーターは、呼び出し元がパラメーターを渡すために使用されるレジスターにコピーされ、呼び出し先がローカル変数にコピーします。したがって、ローカル変数がメモリ位置を持っているかどうかは、呼び出し先の問題のみです。

一方、コピーまたは移動コンストラクターを使用する必要がある型では、コピー操作をこのように分割できないため、メモリに渡す必要があります。

C ++標準委員会は、この点について近年、またはこれまでに議論しましたか?

標準化団体がこれを検討したかどうかはわかりません。

私への明白な解決策は、適切な破壊的な動き(「有効だがそれ以外は指定されていない状態」の現在の中途半端な家ではなく)を言語に追加し、タイプに「些細な破壊的な動き」を許可するフラグを付ける方法を導入することです「それは些細なコピーを許可していなくても。

ただし、このようなソリューションでは、既存のコードを実装するために既存のコードのABIを破壊する必要があるため、かなりの抵抗が生じる可能性があります(ただし、新しいC ++標準バージョンの結果としてのABIの破壊は前例のないものではなく、たとえばstd :: stringの変更など) C ++ 11では、ABIブレークが発生しました。


適切な破壊的な移動により、unique_ptrをレジスタに渡すことができる方法について詳しく説明できますか?それは、アドレス可能なストレージの要件を削除できるためでしょうか?
einpoklum

適切な破壊的な動きは、些細な破壊的な動きの概念を導入することを可能にします。これにより、今日のささいなコピーができるのと同じ方法で、ABIによってささいな動きを分割することができます。
プラグウォッシュ

コンパイラがパラメータパスを通常の移動またはコピーとして実装し、その後に「単純な破壊的移動」を実装して、パラメータがどこから来た場合でも常にレジスタを渡すことができるようにするルールを追加することもできます。
プラグウォッシュ

レジスタサイズはポインタを保持できますが、unique_ptr構造ですか?sizeof(unique_ptr <T>)とは何ですか?
Mel Viso Martinez

@MelVisoMartinez混乱unique_ptrしてshared_ptrセマンティクスになる可能性がありshared_ptr<T>ます:ctorに提供することができます1)派生オブジェクトUへのptr xを静的タイプUで削除しますdelete x;(式があるため、ここでは仮想dtorは必要ありません)2)またはカスタムクリーンアップ関数も。つまり、実行時の状態がshared_ptr制御ブロック内で使用され、その情報がエンコードされます。OTOHにunique_ptrはそのような機能はなく、状態の削除動作をエンコードしません。クリーンアップをカスタマイズする唯一の方法は、別のテンプレートインスタンス(別のクラスタイプ)を作成することです。
curiousguy

-1

最初に、値渡しと参照渡しの意味に戻る必要があります。

JavaやSMLのような言語の場合、変数の値をコピーするのと同じように、値による受け渡しは単純です(参照による受け渡しはありません)。すべての変数は単なるスカラーであり、組み込みのコピーセマンティクスがあるため、算術としてカウントされます。 C ++、または「参照」(名前と構文が異なるポインター)で入力します。

Cでは、スカラー型とユーザー定義型があります。

  • スカラーには、コピーされる数値または抽象値(ポインターは数値ではなく、抽象値があります)があります。
  • 集約型には、初期化されている可能性のあるすべてのメンバーがコピーされています。
    • 製品タイプ(配列と構造体)の場合:再帰的に、構造体のすべてのメンバーと配列の要素がコピーされます(C関数の構文では、値で配列を直接渡すことはできません。構造体の配列メンバーのみが可能ですが、詳細です。 )。
    • 合計タイプ(ユニオン)の場合:「アクティブメンバー」の値が保持されます。明らかに、すべてのメンバーを初期化できるわけではないため、メンバーごとのコピーは適切ではありません。

C ++では、ユーザー定義型にユーザー定義のコピーセマンティックを含めることができます。これにより、リソースの所有権と「ディープコピー」操作を持つオブジェクトを使用して、真に「オブジェクト指向」プログラミングが可能になります。このような場合、コピー操作は、実際にはほとんど任意の操作を実行できる関数の呼び出しです。

C ++としてコンパイルされたC構造体の場合、「コピー」は、コンパイラーによって暗黙的に生成されるユーザー定義のコピー操作(コンストラクターまたは代入演算子のいずれか)の呼び出しとして定義されます。これは、C / C ++共通サブセットプログラムのセマンティクスがCとC ++で異なることを意味します。Cでは集約タイプ全体がコピーされ、C ++では暗黙的に生成されたコピー関数が呼び出されて各メンバーをコピーします。最終結果として、どちらの場合も各メンバーがコピーされます。

(ユニオン内の構造体がコピーされる場合は、例外があると思います。)

したがって、クラス型の場合、新しいインスタンスを作成する唯一の方法(ユニオンコピーの外)は、コンストラクタを使用することです(簡単なコンパイラで生成されたコンストラクタがある場合でも)。

単項演算子を介して右辺値のアドレスを取得することはできませんが、&右辺値オブジェクトがないことを意味するわけではありません。そしてオブジェクトは、定義により、アドレスを持っています。また、そのアドレスは構文構造によっても表されます。クラス型のオブジェクトはコンストラクターによってのみ作成でき、thisポインターを持っています。しかし、単純な型の場合、ユーザーが作成したコンストラクターがないためthis、コピーが構築されて名前が付けられるまで配置する場所がありません。

スカラー型の場合、オブジェクトの値はオブジェクトの右辺値であり、オブジェクトに格納される純粋な数学的値です。

クラス型の場合、オブジェクトの値の唯一の概念は、オブジェクトの別のコピーであり、これはコピーコンストラクター、つまり実際の関数によってのみ作成できます(単純な型の場合、関数は非常に単純ですが、これらは場合によってはコンストラクタを呼び出さずに作成されます)。つまり、objectの値は、実行によるグローバルプログラム状態の変更の結果です。数学的にはアクセスしません。

したがって、値による受け渡しは実際には重要ではありません。コピーコンストラクタコールによる受け渡しですが、それほど美しくありません。コピーコンストラクターは、オブジェクトタイプの適切なセマンティクスに従って、内部不変式(組み込みのC ++プロパティではなく、抽象的なユーザープロパティ)を考慮して、適切な「コピー」操作を実行することが期待されています。

クラスオブジェクトの値渡しとは、次のことを意味します。

  • 別のインスタンスを作成する
  • 次に、呼び出された関数をそのインスタンスに作用させます。

この問題は、コピー自体がアドレスを持つオブジェクトであるかどうかとは関係がないことに注意してください。すべての関数パラメーターはオブジェクトであり、(言語セマンティックレベルで)アドレスを持っています

問題は次のいずれかです。

  • コピーは、で初期化され新しいオブジェクトですスカラーと同様に、元のオブジェクトの純粋な数学的値(真の純粋な右辺値)でです。
  • またはクラスと同様に、コピーは元のオブジェクトの値です

自明なクラス型の場合でも、オリジナルのメンバーコピーのメンバーを定義できるため、コピー操作(コピーコンストラクターと代入)の自明性のため、オリジナルの純粋な右辺値を定義できます。任意の特別なユーザー関数ではそうではありません:オリジナルの値は構築されたコピーでなければなりません。

クラスオブジェクトは呼び出し元が作成する必要があります。コンストラクターは正式にthisポインターを持っていますが、ここでは形式は関係ありません。すべてのオブジェクトは正式にアドレスを持っていますが、実際に非純粋にローカルな方法で使用されるアドレスのみを取得するオブジェクト(*&i = 1;純粋にローカルでのアドレスの使用とは異なります)だけが明確に定義されている必要があります住所。

オブジェクトは、これら2つの別々にコンパイルされた関数の両方でアドレスを持っているように見える必要がある場合、絶対にアドレスで渡される必要があります。

void callee(int &i) {
  something(&i);
}

void caller() {
  int i;
  callee(i);
  something(&i);
}

ここでsomething(address)、純粋な関数やマクロ、またはprintf("%p",arg)アドレスを格納できない、または別のエンティティと通信できないもの(など)であっても、アドレスは一意のオブジェクトに対して明確に定義する必要があるため、アドレスで渡す必要があります。intユニークを有します身元。

外部関数に渡されるアドレスに関して、外部関数が「純粋」であるかどうかはわかりません。

ここでは潜在的な非自明なコンストラクタやデストラクタのいずれかのアドレスの実際の使用のために、発信者側は、おそらく安全な、単純なルートを取った理由であると、呼び出し元のオブジェクトにアイデンティティを与えるとして、そのアドレスを渡すことが可能必ず施工後のコンストラクタで、そのアドレスのいずれかの非自明な使用、ということとデストラクタでは一貫しているthisオブジェクトの存在の上に同じように表示される必要があります。

自明ではないコンストラクタやデストラクタは、他の関数と同様に、this自明でないものを持ついくつかのオブジェクトがそうでなくても、値に対する一貫性を必要とする方法でポインタを使用できます。

struct file_handler { // don't use that class!
    file_handler () { this->fileno = -1; }
    file_handler (int f) { this->fileno = f; }
    file_handler (const file_handler& rhs) {
        if (this->fileno != -1)
            this->fileno = dup(rhs.fileno);
        else
            this->fileno = -1;
    }
    ~file_handler () {
        if (this->fileno != -1)
            close(this->fileno); 
    }
    file_handler &operator= (const file_handler& rhs);
};

その場合、ポインターの明示的な使用(明示的な構文this->)に関係なく、オブジェクトIDは無関係であることに注意してください。コンパイラーは、オブジェクトをビット単位でコピーして移動し、「コピー削除」を行うことができます。これはthis、特別なメンバー関数での使用の「純度」のレベルに基づいています(アドレスはエスケープしません)。

ただし、純正度は標準宣言レベルで使用可能な属性ではないため(インライン関数以外の宣言に純正度の説明を追加するコンパイラー拡張が存在するため)、使用できない可能性があるコードの純正度に基づいてABIを定義することはできません(コードは、インラインでなく、分析に使用できない場合があります)。

純度は「確かに純粋」または「不純または未知」として測定されます。共通基盤、つまりセマンティクスの上限(実際には最大)、またはLCM(最小公倍数)は「不明」です。したがって、ABIは未知数で解決します。

概要:

  • 一部の構文では、オブジェクトIDを定義するためにコンパイラーが必要です。
  • ABIはプログラムのクラスの観点から定義されており、最適化される可能性のある特定のケースではありません。

可能な将来の仕事:

純粋性注釈は、一般化および標準化するのに十分役立ちますか?


1
最初の例は誤解を招くようです。あなたは一般的に言っているだけだと思いますが、最初は問題のコードにたとえていると思いました。ただし、値によってvoid foo(unique_ptr<int> ptr)クラスオブジェクトを取得します。そのオブジェクトにはポインターメンバーがありますが、ここでは参照によって渡されるクラスオブジェクト自体について話しています。(それは自明なコピーができないので、そのコンストラクタ/デストラクタは一貫性を必要とするためthisです。)これは実際の引数であり、明示的に参照渡しする最初の例に接続されていません。その場合、ポインタはレジスタで渡されます。
Peter Cordes

@PeterCordes「あなたは問題のコードにたとえていました。」私はまさにそうしました。" 値によるクラスオブジェクト "はい、おそらくクラスオブジェクトの "値"などは存在しないため、非数学型の値による"値による"ではないことを説明する必要があります。「そのオブジェクトにはポインタメンバーがあります」「スマートptr」のptrのような性質は無関係です。「スマートptr」のptrメンバーも同様です。ptrは単なるのようなスカラーintです。「所有権」が「ptrの運搬」とは関係がないことを示す「smart fileno」の例を書きました。
curiousguy

1
クラスオブジェクトの値は、そのオブジェクト表現です。の場合unique_ptr<T*>、これはT*レジスタと同じサイズとレイアウトで、レジスタに収まります。簡単にコピーできるクラスオブジェクトは、ほとんどの呼び出し規約と同様に、x86-64 System Vのレジスタで値によって渡すことができます。これは作るコピーunique_ptrあなたのとは異なり、オブジェクトをint呼び出し先の例が&i あり、発信者のアドレスがありますiあなたが参照によって渡されるので、C ++レベルではないだけのasm実装の詳細として、。
Peter Cordes

1
エラー、私の最後のコメントの修正。オブジェクトのコピーを作成するだけではありませんunique_ptr。それを使用しているstd::moveので、同じの2つのコピーが作成されないため、コピーしても安全unique_ptrです。しかし、自明にコピー可能な型の場合、はい、それは集合オブジェクト全体をコピーします。それが単一のメンバーである場合、適切な呼び出し規約では、その型のスカラーと同じように扱われます。
Peter Cordes

1
よく見えます。注:C ++としてコンパイルされたC構造体の場合 -これは、C ++の違いを紹介するのに役立つ方法ではありません。C ++ではstruct{}C ++構造体です。おそらく、「プレーンな構造体」または「Cとは異なり」と言うべきでしょう。はい、違いがあります。atomic_int構造体メンバーとして使用すると、Cはそれを非原子的にコピーします。削除されたコピーコンストラクターでC ++エラーが発生します。C ++がvolatileメンバーを持つ構造体に対して行うことを忘れています。Cはstruct tmp = volatile_struct;全部をコピーするようにさせます(SeqLockに便利です)。C ++はしません。
Peter Cordes
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.