[解決済み] T*はレジスタに渡せるのに、unique_ptr<T>は渡せないのはなぜですか?
質問
CppCon 2019でChandler Carruthのトークを見ています。
この中で、彼は
std::unique_ptr<int>
の上に
int*
このセグメントは17:25に始まります。
を見ることができます。 コンパイル結果 を見てください。コンパイラは unique_ptr 値 (実際には一番下の行では単なるアドレス) をレジスタ内に渡すことはせず、ストレート メモリ内にのみ渡すようです。
Carruth 氏が 27:00 頃に指摘したことの 1 つは、C++ ABI は値によるパラメーター (すべてではありませんが、おそらく非プリミティブ型、非自明構成型) を、レジスタ内ではなくメモリ内で渡すよう要求しているということです。
私の質問です。
- これは実際にいくつかのプラットフォームでの ABI 要件なのでしょうか?(どの?) それとも、特定のシナリオで何らかの悲観的なものなのでしょうか?
- なぜ ABI はそのようになっているのでしょうか?つまり、構造体/クラスのフィールドがレジスタ内に、あるいは単一のレジスタに適合する場合、なぜそのレジスタ内でそれを渡すことができないのでしょうか?
- 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));
}
どのように解決するのですか?
<ブロッククオート- これは実際に ABI の要件なのでしょうか、それとも特定のシナリオで悲観的になっているだけなのでしょうか。
1 つの例として System V Application Binary Interface AMD64 Architecture Processor Supplement (英語) . これは64ビットx86互換CPU(Linux x86_64 architecure)用のABIです。Solaris, Linux, FreeBSD, macOS, Windows Subsystem for Linuxで使用可能です。
C++ オブジェクトが自明でないコピーコンストラクタまたは自明でないデストラクタを持つ場合、そのオブジェクトは C++ オブジェクトに渡されます。 C++ オブジェクトに非自明なコピー コンストラクタまたは非自明なデストラクタがある場合、それは不可視参照で渡されます (オブジェクトは、パラメータ リスト内で オブジェクトはクラス INTEGER を持つポインタによってパラメータ リスト内で置き換えられます)。
非自明なコピーコンストラクタまたは非自明なデストラクタを持つオブジェクトは、値で渡すことができません。 なぜなら、そのようなオブジェクトは明確に定義されたアドレスを持っていなければならないからです。同様の問題は 関数からオブジェクトを返す場合にも同様の問題があります。
些細なコピーコンストラクタと些細なデストラクタを持つ1つのオブジェクトを渡すために、2つの汎用レジスタしか使用できないことに注意してください。
sizeof
を持つオブジェクトの値のみがレジスタに渡されます。参照
呼び出しの規約 by Agner Fog
特に§7.1 オブジェクトの受け渡しと返却を参照してください。レジスタの SIMD 型を渡すための別の呼び出し規約があります。
他の CPU アーキテクチャのために異なる ABI があります。
また Itanium C++ ABI もあり、これはほとんどのコンパイラが準拠しています (MSVC は別として) が、これは が必要とする :
パラメータの型が呼び出しの目的のために自明でない場合、呼び出し側はテンポラリのためのスペースを確保し、そのテンポラリを参照渡ししなければなりません。
以下の場合、型は呼び出しの目的のために非自明であるとみなされます。
- 非自明なコピーコンストラクタ、ムーブコンストラクタ、デストラクタを持つか、または
- コピーとムーブのコンストラクタがすべて削除される。
この定義は、クラス型に適用され、型を渡したり返したりするときに余分な一時が許される型の [class.temporary]p3 における定義を補完することを意図しています。ABI の目的のために些細な型は、レジスタなど、ベース C ABI の規則に従って渡され、返されます。
<ブロッククオート
- なぜ ABI はこのような仕様になっているのでしょうか。つまり、構造体/クラスのフィールドがレジスタ内に、あるいは単一のレジスタに収まるのであれば、なぜそのレジスタ内で渡すことができないのでしょうか?
これは実装の詳細ですが、例外が処理されるとき、スタック巻き戻し中に、破壊される自動保存期間を持つオブジェクトは、その時間までにレジスタが破壊されているため、関数スタックフレームに対してアドレス指定可能でなければなりません。スタック巻き戻しコードは、それらのデストラクタを呼び出すためにオブジェクトのアドレスを必要としますが、レジスタ内のオブジェクトはアドレスを持っていません。
衒学的に デストラクタはオブジェクトに対して動作します。 :
オブジェクトは、構築時([class.cdtor])、寿命時、破棄時にストレージの領域を占有します。
がない場合、C++ではオブジェクトは存在できません。 アドレス指定可能な ストレージが割り当てられていない場合、オブジェクトは存在できません。 オブジェクトの ID はそのアドレスであるため .
レジスタに保持された些細なコピーコンストラクタを持つオブジェクトのアドレスが必要な場合、コンパイラは単にオブジェクトをメモリに格納してアドレスを取得することができます。一方、コピーコンストラクタが自明でない場合、コンパイラは単にメモリに格納することはできず、むしろ参照を取るコピーコンストラクタを呼び出す必要があり、したがって、レジスタ内のオブジェクトのアドレスが必要になります。呼び出しの規約はおそらく、コピー コンストラクタが呼び出し側でインライン化されているかどうかに依存することはできません。
このことについて考えるもう一つの方法は、自明なコピー可能型に対して、コンパイラが 値 をレジスタに転送し、そこから必要であればプレーンメモリストアによってオブジェクトを復元することができる、ということです。例えば
void f(long*);
void g(long a) { f(&a); }
を x86_64 で System V 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 は適用されないことに注意してください。リンク時のコード生成と同様に、コンパイラーは他の翻訳ユニットで定義された関数をインライン化したり、カスタムの呼び出し規約を使用したりすることができます。
関連
-
[解決済み】C++ - 解放されるポインタが割り当てられていないエラー
-
[解決済み】C++でランダムな2倍数を生成する
-
[解決済み】Cygwin Make bash コマンドが見つかりません。
-
[解決済み】システムが指定されたファイルを見つけられませんでした。
-
[解決済み】 while(cin) と while(cin >> num) の違いは何ですか?)
-
[解決済み】警告 - 符号付き整数式と符号なし整数式の比較
-
[解決済み】変数やフィールドがvoid宣言されている
-
[解決済み] スタックアロケーションにより初期化されていない値が作成された
-
[解決済み] なぜテンプレートはヘッダーファイルでしか実装できないのですか?
-
[解決済み】vectorにunique_ptrをpush_backできないのはなぜですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】C++エラー。アーキテクチャ x86_64 に対して未定義のシンボル
-
[解決済み】変数 '' を抽象型 '' と宣言できない。
-
[解決済み] error: 'if' の前に unqualified-id を期待した。
-
[解決済み】「corrupted size vs. prev_size」glibc エラーを理解する。
-
[解決済み】エラー:strcpyがこのスコープで宣言されていない
-
[解決済み】クラスのコンストラクタへの未定義参照、.cppファイルの修正も含む
-
[解決済み] gdbを使用してもデバッグシンボルが見つからない
-
[解決済み】Enterキーを押して続行する
-
[解決済み】VC++の致命的なエラーLNK1168:書き込みのためにfilename.exeを開くことができません。
-
[解決済み】なぜC++にはガベージコレクタがないのですか?