コンパイラは、文字を追加する際に未使用の文字列を最適化しなくなる
質問
私は、なぜ次のコードの部分があるのかが気になります。
#include <string>
int main()
{
std::string a = "ABCDEFGHIJKLMNO";
}
でコンパイルした場合
-O3
でコンパイルすると,次のようなコードになります.
main: # @main
xor eax, eax
ret
(未使用の
a
が不要であることは完全に理解していますので、コンパイラは生成されるコードからこれを完全に省くことができます)
しかし、次のようなプログラムがあります。
#include <string>
int main()
{
std::string a = "ABCDEFGHIJKLMNOP"; // <-- !!! One Extra P
}
の収量。
main: # @main
push rbx
sub rsp, 48
lea rbx, [rsp + 32]
mov qword ptr [rsp + 16], rbx
mov qword ptr [rsp + 8], 16
lea rdi, [rsp + 16]
lea rsi, [rsp + 8]
xor edx, edx
call std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_create(unsigned long&, unsigned long)
mov qword ptr [rsp + 16], rax
mov rcx, qword ptr [rsp + 8]
mov qword ptr [rsp + 32], rcx
movups xmm0, xmmword ptr [rip + .L.str]
movups xmmword ptr [rax], xmm0
mov qword ptr [rsp + 24], rcx
mov rax, qword ptr [rsp + 16]
mov byte ptr [rax + rcx], 0
mov rdi, qword ptr [rsp + 16]
cmp rdi, rbx
je .LBB0_3
call operator delete(void*)
.LBB0_3:
xor eax, eax
add rsp, 48
pop rbx
ret
mov rdi, rax
call _Unwind_Resume
.L.str:
.asciz "ABCDEFGHIJKLMNOP"
とコンパイルすると、同じ
-O3
. ということを認識しないのは理解できない。
a
は、文字列が 1 バイト長くなっても、まだ使われていないことを認識しない理由が理解できます。
この質問は、gcc 9.1 と clang 8.0 に関連するものです(オンライン。 https://gcc.godbolt.org/z/p1Z8Ns 私の観察では、他のコンパイラーは未使用の変数 (ellcc) を完全に削除するか、文字列の長さに関係なくそのためのコードを生成するからです。
どのように解決するのですか?
これは、文字列が小さい場合の最適化に起因します。文字列データがヌルターミネータを含めて16文字以下の場合、ローカルバッファに
std::string
オブジェクト自体のローカルバッファに格納されます。そうでない場合は、ヒープ上にメモリを確保し、そこにデータを格納する。
最初の文字列
"ABCDEFGHIJKLMNO"
とヌルターミネータを足すとちょうど16のサイズになります。追加する
"P"
を追加すると、バッファを越えてしまうので
new
が内部で呼び出され、必然的にシステムコールが発生します。コンパイラは、副作用がないことを保証することが可能であれば、何かを最適化することができます。システム コールはおそらくこれを不可能にします。対照的に、構築中のオブジェクトにローカルなバッファを変更すると、そのような副作用の分析が可能になります。
libstdc++ のバージョン 9.1 でローカル バッファーをトレースすると、以下の部分が明らかになります。
bits/basic_string.h
:
template<typename _CharT, typename _Traits, typename _Alloc> class basic_string { // ... enum { _S_local_capacity = 15 / sizeof(_CharT) }; union { _CharT _M_local_buf[_S_local_capacity + 1]; size_type _M_allocated_capacity; }; // ... };
これは、ローカルバッファのサイズにスポットすることができます
_S_local_capacity
と、ローカルバッファそのもの (
_M_local_buf
). コンストラクタがトリガーを引くと
basic_string::_M_construct
が呼び出されたとき、あなたは
bits/basic_string.tcc
:
void _M_construct(_InIterator __beg, _InIterator __end, ...) { size_type __len = 0; size_type __capacity = size_type(_S_local_capacity); while (__beg != __end && __len < __capacity) { _M_data()[__len++] = *__beg; ++__beg; }
ローカルバッファがその内容で満たされるところです。この部分のすぐ後に、ローカルの容量を使い果たす分岐があります - 新しいストレージが割り当てられます (これは
M_create
のアロケートによって)新しいストレージが割り当てられ、ローカルバッファは新しいストレージにコピーされ、初期化引数の残りで埋められます。
while (__beg != __end) { if (__len == __capacity) { // Allocate more space. __capacity = __len + 1; pointer __another = _M_create(__capacity, __len); this->_S_copy(__another, _M_data(), __len); _M_dispose(); _M_data(__another); _M_capacity(__capacity); } _M_data()[__len++] = *__beg; ++__beg; }
余談ですが、小さな文字列の最適化は、それだけでかなりのトピックです。個々のビットを微調整することで、大規模な違いを生み出すことができることを実感するには
このトーク
. また、この講演では
std::string
に同梱されている実装が
gcc
(libstdc++) が動作し、より新しいバージョンの規格に適合するように過去に変更されました。
関連
-
[解決済み】非静的メンバ関数への参照を呼び出す必要がある
-
[解決済み] エラーが発生する。ISO C++は型を持たない宣言を禁じています。
-
[解決済み] [Solved] Error C1083: Cannot open include file: 'stdafx.h'
-
[解決済み】変数 '' を抽象型 '' と宣言できない。
-
[解決済み】IntelliSense:オブジェクトに、メンバー関数と互換性のない型修飾子がある
-
[解決済み】Visual C++で "Debug Assertion failed "の原因となる行を見つける。
-
[解決済み】Visual Studio 2013および2015でC++コンパイラーエラーC2280「削除された関数を参照しようとした」が発生する
-
[解決済み] to_string は std のメンバーではない、と g++ が言っている (mingw)
-
[解決済み】システムが指定されたファイルを見つけられませんでした。
-
[解決済み] C++0xで "while(1); "を排除する最適化
最新
-
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 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】構造体のベクター初期化について
-
[解決済み】coutはstdのメンバではない
-
[解決済み】非静的メンバ関数への参照を呼び出す必要がある
-
[解決済み】C++エラーです。"配列は中括弧で囲まれたイニシャライザーで初期化する必要がある"
-
[解決済み】C++ 式はポインタからオブジェクトへの型を持っている必要があります。
-
[解決済み】浮動小数点例外エラーが発生する: 8
-
[解決済み】なぜ、サイズ8の初期化されていない値を使用するのでしょうか?
-
[解決済み】Enterキーを押して続行する
-
[解決済み】変数やフィールドがvoid宣言されている
-
[解決済み] std::stringの文脈における頭文字SSOの意味