技術情報ブログ
技術情報ブログ C++
C++という言語について
2023/09/12
かねてから SNS やブログで「C++ は難しい。」といった内容の
投稿や記事を目にすることがあります。
正直私はピンと来ません。
20年近く「C++」をメインに多くのプログラミング言語に触れてきましたが、どの言語もちゃんと難しいです。
日本語も世界でトップクラスに難しい言語と言われますが、多くの日本人はそう捉えてはいないと思います。
しかしながら、比較的機械語に近い形である「C言語」がベースなっていて、
世のエンジニアの多くが躓きがちな「ポインタ」と「オブジェクト指向」という2つの概念の理解が必要。
と書いてみると確かに少し技術的なハードル自体は高い印象は受けますね...。
今回は、そんな「C++」について少し深堀りしてみたいと思います。
目次
- 他の言語と比べてどういう特徴があるか
- どういった分野、モノに使われているか
- なぜ「難しい」と言われているのか
- 余談
----------------------------------------------------------------------------------------------------
1. 他の言語と比べてどういう特徴があるか
「C++」の特徴は細かいものも含めるとたくさんあるとは思いますが、
私がこれまでの業務経験の中で強く感じたことを2つ挙げたいと思います。
~自由度が高く、できることが多い~
「好きなことを思い通りに実現できる。」というのが私の「C++」に対するイメージです。
実際、大規模なシステム開発では「C++」を採用していることが多いです。
その反面、実現するための"準備"や"後始末"も全てエンジニアが面倒を見る必要があるため、
ソースコードが長く複雑になりやすい、というデメリットもあります。
その"準備"や"後始末"に不備があることにより思わぬ障害が発生してしまった経験もあります。
~処理速度が速い~
私は業務で大量の数学的な演算を行うことが多いのですが、それらの処理は
「Java」や「C#」といった他の言語と比べると、少なくとも 1.5~2倍 は速いと感じます。
(このあたりは言語によって得意不得意があるのかもしれません。)
また、処理速度だけでなくメモリの消費量も少ないというメリットもあります。
これらは「C++」が持つ「メモリ空間を直接操作することができる」という特性から来ていますが、
この特性はシステムの脆弱性にも繋がっており、所謂「バッファオーバーフロー攻撃」の標的にされる可能性があります。
この「バッファオーバーフロー攻撃」はプログラム側で対策が可能すが、
「C++」ではメモリ(バッファ)管理がエンジニアに委ねられているため、
脆弱性を脆弱なままにしないようプログラミングに気をつける必要があります。
----------------------------------------------------------------------------------------------------
2. どういった分野、モノに使われているか
「1.」で取り上げたようにできることが多く、
『Webブラウザなどアプリケーションの開発』から『ロボットの制御』まで、
幅広い分野で使用されていて、最近なにかと話題のAI分野でも使用されていると聞きます。
皆さんの身近なところでいうと、『Windows』『Microsoft Office』『Google Chrome』も
「C++」を使用して作成されています。
よくゲームをプレイされる方だと、起動時に『Unreal Engine』というロゴを目にすることが多いと思いますが、
これはゲーム製作に特化したエンジンのことで、これも主に「C++」を使用して作成されています。
『自動車の自動運転技術』の開発も主に「C++」が使用されています。
これらはいずれも、実行速度を重視する必要があったり、
最低限の物理メモリで複雑な処理を実現する必要があったりするもので、
先に挙げた「C++」の特徴に合致していますよね。
----------------------------------------------------------------------------------------------------
3. なぜ「難しい」と言われているのか
冒頭に書いたように「C++」ならではの難しさというものにピンと来ていないのですが、
以下の点が難しいと言われる要因なのではないかと考えました。
~ガベージコレクションがない~
これは「2.」で挙げた"後始末"についての話になります。
動的に確保したメモリは、不要になったら解放しないとその分のメモリを占有し続けてしまい、
長時間稼働するシステムの場合、それが原因で動作が遅くなったり停止してしまったりします。
この「不要になったら」というのをを自動で検知してメモリを解放してくれるのがガベージコレクションであり、
「Java」や他の言語にはあるのですが、「C++」にはこの機能はありません。
ですので、確保したメモリが不要になるタイミングを意識してプログラミングする必要があります。
ただし、「C++」でもSTLやスマートポインタを駆使することで、ガベージコレクションの代用は可能です。
~自由度が高すぎる~
「何らかの処理を実現するための方法」が無数にあることにより、
逆にどの方法を採れば良いのかが分からなくなってしまうことがあります。
また、エンジニアによってどの方法を採るのかの基準も異なるため、
他者が書いたソースコードを読むと全く別の言語に見えることがあります。
~テンプレート~
汎用的なプログラムを作成できる「C++」独自の概念です。
使いこなせると処理の再利用がしやすくなり、ソースコードの量を大幅に減らすことができます。
はっきり言ってこれは難しいです。(私は使いこなせていません。)
テンプレートを使わないと実現できないようなことはほとんどないですが、
その場合はソースコードの複雑化と可読性の低下は避けられないです。
----------------------------------------------------------------------------------------------------
4. 余談
近年「C++」に変わるものとして「Rust」というプログラミング言語が注目されており、
私はまだ触れたことはないのですが、こちらも「C++」と同様に処理速度やメモリ消費量に優れているようです。
ですので、もしかすると近い将来では大規模システムの開発は「Rust」が主流になるかもしれません。
しかし、上に挙げた『ロボットの制御』や『自動車の自動運転技術』といったような、限られたリソース内で高効率の処理が求められる分野では、
「ポインタ」と言うメモリに直接アクセスする独自の技術仕様が非常に有効なので、まだまだ「C++」は活用され続けることでしょう。
最後に、私は「C++」が好きなので消えて欲しくないと思っています。
C/C++ のコンパイル時最適化に注意する
2022/08/19
はじめまして、富山オフィスの田財です。
私は普段 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 で最適化した結果を逆アセンブルで見てみます。
注目は
の部分です。
・10 の階乗を何度も計算する必要が無いこと
・この計算結果は常に変わらないこと
を見事看破したコンパイラは、コンパイル時に
の部分をあらかじめ計算してしまい、x375F00(10進数で 3,628,800) という数字をそのまま使った処理にしています。
この数字を使って、出力に使用する変数が x229B600(10進数で 36,288,000) になるまで都度足しては出力する、
という内容に最適化したようです。
最適化した結果をソースコードに直して比較すると、以下のようになるでしょうか。
○最適化前
○最適化後
こちらが想定していた最適化案よりも、さらに効率が良いですね!
このように、コンパイラの最適化はとても賢いです。
コンパイラの最適化機能はどんどん発展しており、プログラマがあまり意識せずとも
高速に動作するプログラムが作成できるようになってきています。
さて、ようやく本題です。
上の例で示した通りとても賢い最適化機能ですが、一方で、
この機能によってプログラマが意図しないプログラムになってしまうこともあり、注意が必要です。
今回は、その1例を紹介します。
例えば、以下のようなソースコードがあります。
パスワードを char pwd に代入して操作し終わった後、
速やかに memset で 0 初期化するソースコードです。
いつまでもメモリ上に残存していることは、セキュリティ上リスクがあるためにこのようにしています。
さて、これが最適化されるとどうなるでしょうか。
先ほどと同じく、VisualStudio 2015 で、 /O2 で最適化した結果を逆アセンブルで見てみます。
memset の下に命令が何もありません。
メモリを覗いてみると、return 0 まで辿りついても当然、パスワード("abcd")が残ってしまっています。
これはどういうことでしょうか。
pwd は if (GetPassword(pwd)) {} 以降一切利用されない変数です。
コンパイラ目線で見れば、プログラムが終了したら勝手に開放されるのだから、
今後利用されない変数 pwd をわざわざ 0 初期化しなくても良いはずだ、と判断できます。
そのため、最適化によって memset~ の一文はコンパイルせずにスキップしてしまっています。
このように、プログラマの意図しない最適化により、セキュリティ上リスクのある
プログラムになってしまったり、想定しない動作を引き起こすことがあるのです。
今回の例の回避策として、例えば以下のようにします。
最適化結果を逆アセンブルで見てみます。
今回はちゃんと命令がありますね。
メモリを覗いてみると、
のタイミングで、1変数ずつ0初期化されていっています。
↓
無事、すべて 0 初期化されました。
Windows API には、最適化によってスキップされないことを保証する
ZeroMemory(), SecureZeroMemory() があります。
これらを使うことで、意図しないスキップを回避することが出来ます。
コンパイル時最適化には、他にもプログラマが意図しない結果を引き起こすものがあります。
他の事例は機会があればまたいずれ。
ハイテックスではこのような事例も考慮して、日々開発を行っています!
C++ CString 文字列比較ってどれが速いの?
2022/05/11
はじめまして、富山オフィスの小坂です。
今年の4月で入社11年目になりました。
自分はまだまだ若い気持ちでいますが、新入社員の方を見ると歳を感じますね...。
さて、久しぶりではありますが技術ブログの更新です。
前回の更新が5年前のようで...スミマセン。
今年の4月で入社11年目になりました。
自分はまだまだ若い気持ちでいますが、新入社員の方を見ると歳を感じますね...。
さて、久しぶりではありますが技術ブログの更新です。
前回の更新が5年前のようで...スミマセン。
復帰第1回目のテーマは...
「C++ CString 文字列比較ってどれが速いの?」
「C++ CString 文字列比較ってどれが速いの?」
Windowsのアプリ開発でよく使われているライブラリと言えば「MFC」が思い浮かぶと思います。(もしくはATL)
更にその中で、文字列を扱うクラスと言えば「CString」がありますね。
サイズを気にせず代入や結合が簡単にでき、フォーマットや文字列比較もできるという、至れり尽くせりな便利クラスです。
ということで、今回は「CString」について紹介していきたいと思います。
といっても全部紹介していると日が暮れるので「文字列比較」、更にはその「処理速度」にスポットを当てて話をしたいと思います。
更にその中で、文字列を扱うクラスと言えば「CString」がありますね。
サイズを気にせず代入や結合が簡単にでき、フォーマットや文字列比較もできるという、至れり尽くせりな便利クラスです。
ということで、今回は「CString」について紹介していきたいと思います。
といっても全部紹介していると日が暮れるので「文字列比較」、更にはその「処理速度」にスポットを当てて話をしたいと思います。
文字列比較でよく見るコードは以下のような比較演算子を使った形ですね。
上記のようなコードでもプログラム的に問題はありません。
しかし処理速度の観点から見ると、もっと良いコードが書けます。
ではどのようなコードが速いのか?
検証を行ってみたので、その結果を紹介したいと思います。
- CString str1 = _T("abc");
- CString str2 = _T("123");
- bool bSame = false;
- if (str1 == str2) {
- bSame = true;
- }
上記のようなコードでもプログラム的に問題はありません。
しかし処理速度の観点から見ると、もっと良いコードが書けます。
ではどのようなコードが速いのか?
検証を行ってみたので、その結果を紹介したいと思います。
【検証内容】
以下の文字列の比較を 1,000,000回 行った速度を計測。
Vdk4jQnifyheeUVSJMAD2FdUXyCfR9HRTACieCMBVDguC3ALbU
vDK4JqNIFYHEEuvsjmad2fDuxYcFr9hrtacIEcmbvdGUc3alBu
使用した関数は以下の4つ。
Vdk4jQnifyheeUVSJMAD2FdUXyCfR9HRTACieCMBVDguC3ALbU
vDK4JqNIFYHEEuvsjmad2fDuxYcFr9hrtacIEcmbvdGUc3alBu
使用した関数は以下の4つ。
- Cstring::Compare
- Cstring::CompareNoCase
- _tcscmp
- CStringの等価演算子(==)
【検証コード】
比較結果をbool型の変数に代入するだけのシンプルなコードです。
測定する関数以外のコードはコメントアウトしています。
測定する関数以外のコードはコメントアウトしています。
- bool bSame = false;
- CString str1 = _T("Vdk4jQnifyheeUVSJMAD2FdUXyCfR9HRTACieCMBVDguC3ALbU");
- CString str2 = _T("vDK4JqNIFYHEEuvsjmad2fDuxYcFr9hrtacIEcmbvdGUc3alBu");
- // 測定start
- auto start = std::chrono::system_clock::now();
- for (int i = 0; i < 1000000; i++) {
- //bSame = (0 == str1.Compare(str2));
- //bSame = (0 == str1.CompareNoCase(str2));
- //bSame = (0 == _tcscmp(str1, str2));
- bSame = (str1 == str2);
- }
- // 測定end
- auto end = std::chrono::system_clock::now();
- // ミリ秒→秒 変換
- double dSec = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() * 0.001;
- // 出力
- CString strResult = _T("");
- strResult.Format(_T("%lf"), dSec);
- ::AfxMessageBox(strResult);
【検証結果】
結論:_tcscmpが一番速い
上記の結果を見ると、基本的には「_tcscmp」を使うのが良さそうですね!
- Cstring::Compare 0.030
- Cstring::CompareNoCase 1.380
- _tcscmp 0.003
- CStringの等価演算子(==) 0.037
上記の結果を見ると、基本的には「_tcscmp」を使うのが良さそうですね!
【Tips】
今回の速度計測に用いたライブラリ「std::chrono」を簡単に紹介しておきます。
C++11から導入された標準ライブラリで、現在時刻の取得や時間計測ができます。
以下は処理時間を計測する時のサンプルコードです。
応用すればラップタイムの出力もできるので、ぜひ使ってみてください!
C++11から導入された標準ライブラリで、現在時刻の取得や時間計測ができます。
以下は処理時間を計測する時のサンプルコードです。
- auto start = std::chrono::system_clock::now();
- // この間に測定したい処理を書く
- auto end = std::chrono::system_clock::now();
- // 処理時間を取得(ミリ秒→秒に変換)
- double dSec = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() * 0.001;
応用すればラップタイムの出力もできるので、ぜひ使ってみてください!
以上、いかがでしたでしょうか?
ちょっとテーマがピンポイントすぎたかもしれませんが、皆さんのお役に立てれば幸いです。
次回も乞うご期待ください!
ありがとうございましたm(_ _)m
ちょっとテーマがピンポイントすぎたかもしれませんが、皆さんのお役に立てれば幸いです。
次回も乞うご期待ください!
ありがとうございましたm(_ _)m
« C | 技術情報ブログトップページ | 技術情報ブログの記事一覧 | CAD/CAM/CAE »