今日、/ r / C_Programmingで 、最適化に対するCのconstの効果について質問しました。 過去20年にわたり、この問題に関するオプションを何度も聞いてきました。 個人的に、私はすべてのために命名constを非難します。
次のプログラムを検討してください。
void foo(const int *); int bar(void) { int x = 0; int y = 0; for (int i = 0; i < 10; i++) { foo(&x); y += x;
foo関数はconstへのポインターを受け入れます。これは、 xの値が変更されないことを著者fooに代わって約束します。 コンパイラーは、 x常にゼロ、つまりyも同じであると想定するように思われるかもしれません。
ただし、いくつかの異なるコンパイラによって生成されたアセンブラコードを見ると、ループの各反復でxロードされていることがわかります。 これは、gcc 4.9.2が-O3を使用して生成したもので、私のコメントは次のとおりです。
bar: push rbp push rbx xor ebp, ebp ; y = 0 mov ebx, 0xa ; i sub rsp, 0x18 ; allocate x mov dword [rsp+0xc], 0 ; x = 0 .L0: lea rdi, [rsp+0xc] ; &x call foo add ebp, dword [rsp+0xc] ; y += x ( ?) sub ebx, 1 jne .L0 add rsp, 0x18 ; deallocate x mov eax, ebp ; y pop rbx pop rbp ret
clang 3.5(-fno-unroll-loopsを使用)はほぼ同じことを行い、ebpとebxのみが入れ替わり、 r14で&xの計算がループから抜け出しました。
両方のコンパイラーがこの有用な情報を利用できませんか? foo x変更しますか、未定義の動作ではないでしょうか? 奇妙なことに、答えはノーです。 この状況では 、これはfoo完全に正しい定義になります。
void foo(const int *readonly_x) { int *x = (int *)readonly_x;
constは定数を意味しないことを覚えておくことが重要です。 これは間違った名前であることに注意してください。 これは最適化ツールではありません。 コンパイラーではなくプログラマーに、コンパイル中に特定のクラスのエラーをキャッチするためのツールとして通知する必要があります。 APIで使用する場合、関数が引数をどのように使用するか、または呼び出し側が返されたポインターをどのように処理するかを指示するため、気に入っています。 通常、コンパイラの動作を変更するほど厳密ではありません。
私が言ったことにもかかわらず、コンパイラは constを使用して最適化できる場合があります。 仕様C99、§6.7.3¶5では、これについて1つの提案があります。
const lvalue const, .
元のxはconst修飾子がなかったため、この規則は適用されませんでした。 また、それ自体がconstではないオブジェクトを変更するために、非const型にキャストすることに対するルールはありません。 つまり、上記のfoo動作は、この呼び出しの未定義の動作ではありません。 foo不確実性は、その原因に依存することに注意してください。
bar 1回変更するだけで、このルールを適用可能にし、オプティマイザーを機能させることができます。
const int x = 0;
コンパイラーは、 foo x変更は未定義の動作であると想定できるため、 決して発生しません 。 基本的にこれは、Cオプティマイザーがプログラムについて話す方法です。 コンパイラは、 xが変更されないことを想定し、各反復での読み込みとy両方を最適化できるようにします。
bar: push rbx mov ebx, 0xa ; i sub rsp, 0x10 ; allocate x mov dword [rsp+0xc], 0 ; x = 0 .L0: lea rdi, [rsp+0xc] ; &x call foo sub ebx, 1 jne .L0 add rsp, 0x10 ; deallocate x xor eax, eax ; 0 pop rbx ret
負荷が消え、 y消え、関数は常にゼロを返します。
奇妙なことに、この仕様により、コンパイラはさらに先へ進むことができます。 読み取り専用メモリであっても、スタックのどこかにxを配置できます。 たとえば、彼はそのような変換を行うことができます。
static int __x = 0; int bar(void) { for (int i = 0; i < 10; i++) foo(&__x); return 0; }
または、x86-64( -fPIC、スモールメモリモデル )では、さらにいくつかの命令を取り除くことができます:
section .rodata x: dd 0 section .text bar: push rbx mov ebx, 0xa ; i .L0: lea rdi, [rel x] ; &x call foo sub ebx, 1 jne .L0 xor eax, eax ; 0 pop rbx ret
clangもgccも、ここまでは行きません。これは、コードの記述が不十分だと危険だからです。
constルールに関する特別なルールがあっても、 constを自分と仲間のプログラマーに使用してください。 オプティマイザーに、何が一定で何が一定でないかを自分で決めさせます。