HOME » 技術情報ブログ » C

技術情報ブログ

技術情報ブログ C

はじめまして、富山オフィスの田財です。
私は普段  C++ を利用してシステム開発することが多いので、これに関連したテーマにしたいと思います。
テーマは「C/C++ のコンパイル時最適化に注意する」です。

まず、C/C++を含むプログラミング言語は、言語仕様に従ったソースコードから、
機械語あるいは元のプログラムよりも低い水準のコードに
変換(コンパイル)するコンパイラが存在します。(コンパイラ(Wikipedia)より引用)

また、C/C++の一般的なコンパイラには「最適化」という機能が備わっています。
ソースコードをコンパイルする際に、より高速で動くプログラムとなるように
ソースコードの示す結果が変わらない範囲で最適化してコンパイルする、という機能です。

今回はこのコンパイル時最適化についてのお話ですが、
テーマの本題に入る前に、まずはこの最適化がどのような機能なのか、説明します。

例を1つ提示してみます。


上記のソースコードは、10 の階乗に 0 ~ 9 を順に掛けて、
都度標準出力する単純なものです。
実は、このソースコードには、効率の悪い処理となっている部分があります。

プログラミング経験のある人であれば一目でわかるかもしれませんが、


の部分です。
このソースコードだと、i が 0 ~ 9 の範囲で順に 1 ずつ増えていく間に
何度も 10 の階乗を計算しているため、効率が悪いです。
最適化案としては、「上記の部分を for (int i = 0; i < 10; i++) {} の外に出す」でしょうか。
こんなイメージです。


これであれば、10の階乗の計算を最初に一度行うだけで済みますね。

では、実際にコンパイラに最適化してもらいましょう。
VisualStudio 2015 で、 /O2 で最適化した結果を逆アセンブルで見てみます。

20220819-1.png




注目は

20220819-2.png


の部分です。
・10 の階乗を何度も計算する必要が無いこと
・この計算結果は常に変わらないこと
を見事看破したコンパイラは、コンパイル時に


の部分をあらかじめ計算してしまい、x375F00(10進数で 3,628,800) という数字をそのまま使った処理にしています。
この数字を使って、出力に使用する変数が x229B600(10進数で 36,288,000) になるまで都度足しては出力する、
という内容に最適化したようです。

最適化した結果をソースコードに直して比較すると、以下のようになるでしょうか。

○最適化前

○最適化後

こちらが想定していた最適化案よりも、さらに効率が良いですね!

このように、コンパイラの最適化はとても賢いです。
コンパイラの最適化機能はどんどん発展しており、プログラマがあまり意識せずとも
高速に動作するプログラムが作成できるようになってきています。

さて、ようやく本題です。
上の例で示した通りとても賢い最適化機能ですが、一方で、
この機能によってプログラマが意図しないプログラムになってしまうこともあり、注意が必要です。
今回は、その1例を紹介します。

例えば、以下のようなソースコードがあります。


パスワードを char pwd に代入して操作し終わった後、
速やかに memset で 0 初期化するソースコードです。
いつまでもメモリ上に残存していることは、セキュリティ上リスクがあるためにこのようにしています。

さて、これが最適化されるとどうなるでしょうか。
先ほどと同じく、VisualStudio 2015 で、 /O2 で最適化した結果を逆アセンブルで見てみます。

20220819-3.png

memset  の下に命令が何もありません。
メモリを覗いてみると、return 0 まで辿りついても当然、パスワード("abcd")が残ってしまっています。

20220819-4.png

これはどういうことでしょうか。

pwd は if (GetPassword(pwd)) {} 以降一切利用されない変数です。
コンパイラ目線で見れば、プログラムが終了したら勝手に開放されるのだから、
今後利用されない変数 pwd をわざわざ 0 初期化しなくても良いはずだ、と判断できます。
そのため、最適化によって memset~ の一文はコンパイルせずにスキップしてしまっています。

このように、プログラマの意図しない最適化により、セキュリティ上リスクのある
プログラムになってしまったり、想定しない動作を引き起こすことがあるのです。

今回の例の回避策として、例えば以下のようにします。


最適化結果を逆アセンブルで見てみます。

20220819-5.png

今回はちゃんと命令がありますね。
メモリを覗いてみると、

20220819-6.png


のタイミングで、1変数ずつ0初期化されていっています。

20220819-7.png


20220819-8.png


無事、すべて 0 初期化されました。

20220819-9.png

Windows API には、最適化によってスキップされないことを保証する
ZeroMemory(), SecureZeroMemory() があります。
これらを使うことで、意図しないスキップを回避することが出来ます。

コンパイル時最適化には、他にもプログラマが意図しない結果を引き起こすものがあります。
他の事例は機会があればまたいずれ。

ハイテックスではこのような事例も考慮して、日々開発を行っています!

技術情報ブログトップページ  | 技術情報ブログの記事一覧  | C++ »

お電話でのお問い合せ:076-452-6280

メールフォームでのお問い合わせ・ご相談はこちら

弊社開発実績はこちら

新規開発からシステムリフォームまで、豊富な実績でお応えします。

ソフトウェア開発実績はこちら

製品パッケージのご案内

ワンストップで利用出来るハイテックスのパッケージソフトウェア。

製品パッケージはこちら

ハイテックスの特徴

経験豊富なスタッフがローコストで使いやすいソフトを開発します。

ハイテックスの特徴はこちら

よくある質問

システム開発についての疑問点に丁寧にお応えします。

よくある質問はこちら

料金の目安

ご予算に応じてシステムのご提案をいたします。

料金の目安はこちら

無料相談受付中

専門スタッフがシステムのご提案をいたします。

無料相談お申し込みはこちら

採用情報はこちら

経済産業省認定情報処理支援機関

Copyright Hitechs Inc. All Rights Reserved Worldwide.