はじめに
const
とconstexpr
について、わかりづらかったので、記します。
アセンブリについて、実際に暑かったことはないので、誤解があるかもしれません。何かございましたらご指摘いただけたらと思います。
const
変数を定数とする修飾子。
const T name
(Tにはint
やchar
等, nameには変数名)のように宣言する。
int a = 10;
a = 5; // OK!
const int b = 10;
a = 5; // NG!
constexpr
コンパイル時定数の修飾子。上述のconst
をconstexpr
(const expr
ではない)に書き換えるだけである。コンパイル時に決まっている値にしか用いることができない。
const T name
(Tにはint
やchar
等, nameには変数名)のように宣言する。
const
では、定数使用時にメモリにアクセスするのに対して、constexpr
はコンパイル事にソースコード内に埋め込まれるらしい。
int a = 10;
a = 5; // OK!
constexpr int b = 10;
a = 5; // NG!
本当?
ちょっと興味があり、アセンブリを見てみた(https://godbolt.org を使用)。コンパイラはx86-64 gcc 9.2
C++のソースコード 〜main内で宣言〜
int
、const int
とconstexpr int
をmain文で宣言して、三値を足すプログラムを書いた。
今までの自分のイメージだと、constexpr int c
はメモリ確保せずにソースコード内で10
という値として扱われるものだと考えていた。
int main()
{
int a = 10;
const int b = 10;
constexpr int c = 10;
int d = a + b + c;
return 0;
}
アセンブリ
オプションなし
三値ともメモリ確保されているようである。(あれっ?)
main:
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], 10
mov DWORD PTR [rbp-8], 10
mov DWORD PTR [rbp-12], 10
mov eax, DWORD PTR [rbp-4]
add eax, 20
mov DWORD PTR [rbp-16], eax
mov eax, 0
pop rbp
ret
オプション-O
変数を再利用していないため、最適化によってメモリ確保そのものが消えてしまった。
main:
mov eax, 0
ret
ポインタに値を書き込むようにソースコードにint* p; *p = d;
を追加すると、以下のようにあらかじめ和が算出された値で書き込まれていた。
main:
mov DWORD PTR ds:0, 30
mov eax, 0
ret
C++のソースコード 〜main外で宣言〜
定数の宣言をグローバルで行い、メイン内で計算をしている。今まででは、constexpr
はメモリ確保されず、const
はメモリ確保されると思っていた。
constexpr int a = 10;
const int b = 10;
int main()
{
int c = a + b;
}
アセンブリ(オプションなし)
以下のように、定数は両者ともメモリ確保されておらず、代入ではあらかじめ和が算出された値となっていた。
main:
push rbp
mov rbp, rsp
mov DWORD PTR [rbp-4], 20
mov eax, 0
pop rbp
ret
おわりに
(最適化オプションなしにおける)constexpr
を少し誤解していたので、気づけてよかったなと思いました。コンパイラがすごく賢くてびっくりです。
ただ、今回は変数の再利用がなかったため最適化されているだけかもしれないので、引き続きconstexpr
を使おうと思います。ただし、できるだけグローバルで宣言しようと思います。
また、アセンブリについて、実際に暑かったことはないので、誤解があるかもしれません。何かございましたらご指摘いただけたらと思います。