今日、/ 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; // this load not optimized out } return y; }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
        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; // cast away const (*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
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    を自分と仲間のプログラマーに使用してください。 オプティマイザーに、何が一定で何が一定でないかを自分で決めさせます。