私は最近、#define
とstatic const
の違いは[〜#〜] c [〜#〜]であり、なぜ2つのメソッドが存在するのか疑問に思っていました同じこと。私はここで同様の質問をした人を見つけました:
多くの人がベストプラクティスと慣習について話しているだけでなく、ポインタを定数に渡す必要があるなど、実際にstatic const
を使用しても#define
を使用できない理由など、実際的な理由を説明しています。しかし、私は2つの効率の比較について誰かが話すのをまだ見つけていません。
[〜#〜] c [〜#〜]プリプロセッサについて私が理解していることから、次のようなステートメントがある場合:
#define CONSTANT 6
このように使用できる定数値を作成します
char[CONSTANT]
は、実際にコンパイルされる前に、このステートメントchar[6]
に実際に置き換えられます。
これは、static const constant = 6;
を使用するよりも効率的であるように思われます。これは、#define
よりも手荷物が多いと想定してスタックに存在する定数と呼ばれる変数を作成するためです。プリプロセッサ#define
またはstatic const
ステートメントを使用することを選択できる状況で定数を必要とすると仮定します。明確な理由がないため、どちらを選択するのがより効率的ですか?そして、私はこれを自分でどの程度正確にテストしますか?
次の2つのテストファイルを検討してください。
Test1.c:静的const fooを使用します。
// Test1.c uses static const..
#include <stdio.h>
static const foo = 6;
int main() {
printf("%d", foo);
return 0;
}
Test2.c:マクロを使用します。
// Test2.c uses macro..
#include <stdio.h>
#define foo 6
int main() {
printf("%d", foo);
return 0;
}
gcc -O0
(デフォルト)を使用する場合の対応するアセンブリの同等性は次のとおりです。
Test1.cのアセンブリ:
0000000000000000 <main>:
0: 55 Push rbp
1: 48 89 e5 mov rbp,rsp
4: 48 83 ec 20 sub rsp,0x20
8: e8 00 00 00 00 call d <main+0xd>
d: b8 06 00 00 00 mov eax,0x6
12: 89 c2 mov edx,eax
14: 48 8d 0d 04 00 00 00 lea rcx,[rip+0x4] # 1f <main+0x1f>
1b: e8 00 00 00 00 call 20 <main+0x20>
20: b8 00 00 00 00 mov eax,0x0
25: 48 83 c4 20 add rsp,0x20
29: 5d pop rbp
2a: c3 ret
2b: 90 nop
Test2.cのアセンブリ:
0000000000000000 <main>:
0: 55 Push rbp
1: 48 89 e5 mov rbp,rsp
4: 48 83 ec 20 sub rsp,0x20
8: e8 00 00 00 00 call d <main+0xd>
d: ba 06 00 00 00 mov edx,0x6
12: 48 8d 0d 00 00 00 00 lea rcx,[rip+0x0] # 19 <main+0x19>
19: e8 00 00 00 00 call 1e <main+0x1e>
1e: b8 00 00 00 00 mov eax,0x0
23: 48 83 c4 20 add rsp,0x20
27: 5d pop rbp
28: c3 ret
29: 90 nop
どちらの場合も、外部メモリを使用していません。ただし、#define
はfoo
を値で置き換え、static const
は命令であるため、次の命令への命令ポインタをインクリメントし、値を格納するために1つの追加レジスタを使用するという違いがあります。 。
これにより、静的定数よりマクロの方が優れていると言えますが、その差は最小です。
編集:-O3
コンパイルオプションを使用する場合(最適化時)、test1.cとtest2.cの両方が同じように評価されます。
0000000000000000 <main>:
0: 48 83 ec 28 sub rsp,0x28
4: e8 00 00 00 00 call 9 <main+0x9>
9: 48 8d 0d 00 00 00 00 lea rcx,[rip+0x0] # 10 <main+0x10>
10: ba 06 00 00 00 mov edx,0x6
15: e8 00 00 00 00 call 1a <main+0x1a>
1a: 31 c0 xor eax,eax
1c: 48 83 c4 28 add rsp,0x28
20: c3 ret
21: 90 nop
したがって、gcc
は、最適化時にstatic const
と#define
の両方を同じものとして扱います。
簡単な最適化の質問をテストする簡単な方法は、 godbolt を使用することです。
特定の問題については、最新の最適化コンパイラは両方のケースで同じコードを生成でき、実際にはそれらを定数に最適化するだけです。これは次のプログラムで確認できます(実際に見る):
#include <stdio.h>
#define CONSTANT 6
static const int constant = 6;
void func()
{
printf( "%d\n", constant ) ;
printf( "%d\n", CONSTANT ) ;
}
どちらの場合も、どちらのアクセスも次のように減少します。
movl $6, %esi #,
定数の定義が翻訳から見える場合、コンパイラーはそれを最適化として利用することができます。
これにより、スタックと呼ばれる定数と呼ばれる変数が作成され、#defineよりも手荷物が増えると思います。
複数の場所に「住む」ことができます。コンパイラーは、静的またはスタックのストレージを必要とせずに、参照されている場所で定数を確実に置き換えることができます。
プリプロセッサ#defineまたは静的constステートメントのどちらを使用するかを選択できる状況で定数を必要とすると仮定した場合、明確な理由がありません。
コンパイラとアーキテクチャに依存します。 _#define
_には大きなアドバンテージがあると信じている人もいるようです。そうではありません。明らかなケースは、複雑な評価または関数呼び出し(sin(4.8)
など)です。ループ内で使用される定数を検討してください。適切にスコープされた定数は一度評価されます。defineは反復ごとに評価できます。
そして、私はこれを自分でどの程度正確にテストしますか?
使用する各コンパイラによって生成されたアセンブリを読み取り、測定します。
経験則が必要な場合は、「_#define
_がシナリオで測定可能な改善を提供しない限り、定数を使用する」と言います。
これについては、GCCのドキュメントに良い記事がありました。多分誰かがそれが正確にどこにあったか覚えています。
_static const
_変数はスタックに作成されません(少なくとも作成しないでください)。それらのスペースは、プログラムのロード時に確保されるため、作成に関連する実行時のペナルティはありません。
mayには、初期化に関連する実行時のペナルティがあります。私が使用しているgccのバージョンは、コンパイル時に定数を初期化しますが、その動作がどれほど一般的であるかはわかりません。このような実行時のペナルティがある場合、それはプログラムの起動時に一度だけ発生します。
それを超えて、静的なconst
修飾オブジェクトとリテラルの実行時パフォーマンスの違い1 (これはマクロが最終的に展開されるものです)、リテラルのタイプと関連する操作に応じて、存在しないことは無視できるはずです。
愚かな例(gcc version 4.1.2 20070115 (SUSE Linux)
):
_#include <stdio.h>
#define FOO_MACRO 5
static const int foo_const = 5;
int main( void )
{
printf( "sizeof FOO_MACRO = %zu\n", sizeof FOO_MACRO );
printf( "sizeof foo_const = %zu\n", sizeof foo_const );
printf( " &foo_const = %p\n", ( void * ) &foo_const );
printf( "FOO_MACRO = %d\n", FOO_MACRO );
printf( "foo_const = %d\n", foo_const );
return 0;
}
_
出力:
_sizeof FOO_MACRO = 4
sizeof foo_const = 4
&foo_const = 0x400660
FOO_MACRO = 5
foo_const = 5
_
_foo_const
_のアドレスは、バイナリの_.rodata
_セクションにあります。
_[fbgo448@n9dvap997]~/prototypes/static: objdump -s -j .rodata static
static: file format elf64-x86-64
Contents of section .rodata:
40065c 01000200 05000000 73697a65 6f662046 ........sizeof F
^^^^^^^^
40066c 4f4f5f4d 4143524f 203d2025 7a750a00 OO_MACRO = %zu..
40067c 73697a65 6f662066 6f6f5f63 6f6e7374 sizeof foo_const
40068c 203d2025 7a750a00 20202020 20202666 = %zu.. &f
40069c 6f6f5f63 6f6e7374 203d2025 700a0046 oo_const = %p..F
4006ac 4f4f5f4d 4143524f 203d2025 640a0066 OO_MACRO = %d..f
4006bc 6f6f5f63 6f6e7374 203d2025 640a00 oo_const = %d..
_
オブジェクトはすでに5に初期化されているため、実行時の初期化ペナルティはありません。
printf
ステートメントでは、_foo_const
_の値を_%esi
_にロードするための命令には、リテラル値_0x5
_をロードするためのバイトよりも1バイト多く必要で、命令には_%rip
_レジスタを効果的に逆参照するには:
_400538: be 05 00 00 00 mov $0x5,%esi
^^^^^^^^^^^^^^
40053d: bf ab 06 40 00 mov $0x4006ab,%edi
400542: b8 00 00 00 00 mov $0x0,%eax
400547: e8 e4 fe ff ff callq 400430 <printf@plt>
40054c: 8b 35 0e 01 00 00 mov 270(%rip),%esi # 400660 <foo_const>
^^^^^^^^^^^^^^^^^
400552: bf bb 06 40 00 mov $0x4006bb,%edi
400557: b8 00 00 00 00 mov $0x0,%eax
40055c: e8 cf fe ff ff callq 400430 <printf@plt>
_
これは測定可能なランタイムパフォーマンスの違いにつながりますか?たぶん、正しい状況で。タイトなループでCPUにバインドされた何かを数十万回実行している場合、はい、_static const
_変数に対してマクロ(リテラルに解決される)を使用するとmayかなり高速になります。これがプログラムの存続期間中に一度発生するものである場合、その差は小さすぎて測定できず、_static const
_変数に対してマクロを使用する説得力のある理由はありません。
いつものように、正確性と保守性はパフォーマンスよりも重要です2。マクロの代わりに_static const
_を使用すると、間違いを犯す可能性が低くなります。次のシナリオを検討してください。
_#define FOO 1+2
...
x = FOO * 3;
_
expect、そしてgetはどのような答えになりますか?それと比較して
_static const int foo = 1+2;
...
x = foo * 3;
_
はい、かっこ-_(1 + 2)
_を使用してマクロのケースを修正できます。ポイントは、_static const
_オブジェクトを使用する場合、このシナリオは問題ではありません。足で自分を撃つ方法が1つ少なくなります。