プログラマーが大きな構造体を指定した場合、c/c++ コンパイラは値によって構造体をスタックにプッシュし、数百バイトをスタックに memcopy しますか? 構造体を返すと同じペナルティが発生しますか?
2 に答える
class
はい、ほとんどのコンパイラは、スタックにプッシュするか、値が渡された構造体 (およびes)をスタックにコピーします。これは一般に、コンパイラ、プロセッサ、およびオペレーティング システムに関連するABI (アプリケーション バイナリ インターフェイス) 仕様で必要とされます。
詳細については、たとえばX86 呼び出し規則とSystem V ABI x86-64を参照してください (少なくとも Linux では x86-64 の場合)。
実際には、大きな構造体はスタック上にあり、レジスターを介してそれらへのポインターを (黙って) 渡します。
ABI は、これらの構造が呼び出し元または呼び出し先の呼び出しフレームにあるかどうかを定義します...
2 ワード サイズのstruct
-s の場合、Linux x86-64 ABI はそれらを (引数としても結果としても) ペアのレジスタを介して渡すことがよくあります。
GCCgcc -O -S -fverbose-asm foo.c
を使用して、アセンブリ コードを取得するためにコンパイルしてみてくださいfoo.s
。GCC MELT プローブを使用したりgcc -fdump-tree-all
、内部 (Gimple) 表現を理解することもできます。
内部的に多くのポインターが関与するため、C++の非常に複雑なものclass
は値のサイズが小さい場合があることに注意してください。たとえば、Linux/AMD64 ではsizeof(std::string)
1 (8 バイト) ワード (いくつかの複雑なものへのポインターを含む) であり、おそらくレジスターによって渡されます。同様に、C++ 標準ライブラリの多くのコンテナの値のサイズは小さいです (実際のデータのほとんどはポインタを介して間接的にアクセスされます)。詳細は明らかに実装固有です。
はい、コンパイラはほぼ確実に memcpy のようなことを行い、数百バイトの構造体またはクラスをスタックにコピーします。そうでない場合、次のようなものは機能しません。
std::string s = "A large amount of text";
std::string r = rev(s);
std::cout << s << " reversed is " << r << std::endl;
...
std::string rev(std::string s)
{
std::string::size_type len = s.length();
for(std::string::size_type i = 0; i < len / 2; i++)
{
swap(s[i], s[len-i]);
}
return s;
}
const
これが、オブジェクトへのポインターのみを渡すため、可能な場合は参照を使用することがほぼ常に推奨される理由です。
上記の例は反対されたので、別の例を次に示します。
class mystring
{
char s[200];
size_t len;
public:
mystring(const char *aS)
{
strcpy(s, aS);
len = strlen(s);
}
char& operator[](int index)
{
return s[index];
}
size_t length()
{
return len;
}
}
mystring str("Some long string");
mystring rev = rev_my_str(s);
mystring rev_my_str(mystring s)
{
size_t len = s.length();
for(size_t i = 0; i < len / 2; i++)
{
swap(s[i], s[len-i]);
}
return s;
}
mystring
実際、これにより、スタック上に 2 つのオブジェクト用のスペースが作成s
されrev_my_str
ます。
編集:
g++ -O1
[1] によって上記の呼び出し用に生成されたアセンブラーrev_my_string
。興味深いのは、 、および(それぞれカウント、ソース、宛先)rep movsq
の設定です。$26 は、コピーする 8 バイト単位の数です。26 * 8 = 208 バイト。スタックポインタです。これは、 a が単純な形式でインライン化された場合の外観とほとんど同じです[実際には、アラインされていない開始/終了を処理したり、SSE 命令を使用したりするなど、余分な作業がたくさんある可能性があります]。%ecx
%rsi
%rdi
%rsp
memcpy
memcpy
movl $26, %ecx
movq %rsp, %rdi
movq %rbx, %rsi
rep movsq
leaq 416(%rsp), %rdi
call _Z10rev_my_str8mystring
そして rev_my_string 自体はこんな感じ。rep movsq
関数の下部にある に注意してください。それが、結果の文字列を格納する場所です。
_Z10rev_my_str8mystring:
.LFB990:
.cfi_startproc
movq %rdi, %rax
movq 208(%rsp), %r9
movq %r9, %r10
shrq %r10
je .L5
addq $1, %r10
movl $1, %edx
.L6:
movl %r9d, %ecx
subl %edx, %ecx
leaq 7(%rsp), %rsi
addq %rdx, %rsi
movzbl (%rsi), %edi
movslq %ecx, %rcx
movzbl 8(%rsp,%rcx), %r8d
movb %r8b, (%rsi)
movb %dil, 8(%rsp,%rcx)
addq $1, %rdx
cmpq %r10, %rdx
jne .L6
.L5:
movl $26, %ecx
movq %rax, %rdi
leaq 8(%rsp), %rsi
rep movsq
ret
[1] それより高い最適化を使用すると、コンパイラはコードをインライン化しすぎ (たとえば、rev_my_string 関数がインライン化されます)、何が起こっているのかを確認するのが非常に難しくなります。