2019年12月2日月曜日

コマンドラインからVisual Studioの仕組みを学ぶ



Visual Studioそのものについて解説した本や,Visual C++について解説した本はいくつかあります.ただ,Visual Studioの場合,C#前提だったり,Visual C++についての本でもC++のコードを書くことが前提だったりして,自分でライブラリを作ったり,他のライブラリを利用したりする場合の方法についてはあまり詳しくなかったりします.

このツイートの後に,コマンドラインを覚えれば,色々なIDEの設定などが分かるのでは,という話になったものの,そういった解説って無いよね,という話になったので,ちょっと書いてみようかと.

前提として,Visual Studio 2019でC++の開発環境が入っている前提とします.
また,cdやmkdirなど基本的なコマンドラインの機能についてはこの解説には含めないので,必要に応じて調べてみてください.

コマンドラインを使えるようにする

スタートメニューからVisual Studio 2019のフォルダを探し,Developer Command Prompt for VS 2019かDeveloper PowerShell for VS 2019を起動します.

起動したら,次のコマンドを実行してみましょう.

cl /?

この,cl というのがC++のコンパイラで,/?を渡すことで指定できるオプションの一覧を表示したわけです.

Visual C++の設定の多くは,このclに渡すオプションを生成するために使われます.

そして,ここが肝心なポイントなのですが,色々なC++コンパイラでコマンドラインから指定できるオプションには,共通の機能を提供するものが多々あるのです.

つまり,コマンドラインのオプションが分かっていれば,このオプションを設定するIDEの設定項目はどれか,という探し方ができるので,普段Visual Studioを使っていて,突然Xcodeを使うことになったとしても,何となく設定する方法が分かってしまうのです.

コンパイルしてみる

よくあるHello, Worldをコンパイルしてみましょう.

// main.cpp
#include <iostream>
using namespace std;

int main()
{
    cout << "Hello, World!" << endl;
}
cl main.cpp

コンパイルすると,何やら警告が出ます.着目するポイントは,warning(警告)という単語の直後のC4530という警告の番号です.これをGoogleなどで検索すると,Microsoftのドキュメントが表示されます.

他にもエラーや警告が出た場合,必ずその直後にこういったエラーの種類を示す番号が出ているので,Microsoftのドキュメントで検索してみましょう.エラーや警告の理由が分かれば,修正もしやすくなります.

詳細については省くとして,取り合えず/EHscを指定しろ,ということなので,次のように指定してみましょう.

cl /EHsc main.cpp

これで,問題なくコンパイルできました.同じフォルダにmain.exeとmain.objというファイルができているはずです.

さて,このobjってなんでしょう? これは,C++のソースコードをコンパイルした結果です.これに,標準ライブラリなどの機能をリンクさせて,最終的に実行可能ファイル(exe)になるのです.

簡単なソースコードの場合,コンパイルも一瞬で終わるので気になりませんが,大規模になってくるとコンパイル時間は数十分,数時間,場合によっては一晩かかったりするかもしれません.

できれば,変更した部分だけコンパイルするようにしたいところですよね.IDEは,objとcppを比較して,更新されている場合だけコンパイルする,といったことをすることで,コンパイル時間を短くする手助けをしてくれています.

単純に比較しているわけではなく,ヘッダファイルを書き換えた場合,そのヘッダファイルをインクルードしているcppも変更の対象にしてくれたり,と色々と賢くやってくれています.

ちなみに,この/EHscというのは,プロジェクトのプロパティのC/C++のコード生成のC++の例外を有効にする,で設定できる値です.

インクルードパスを指定する


さて,続いてインクルードパスについて解説します.まずは,次のような構造で必要なファイルを用意してください.

main.cppがあって,subフォルダがあって,その中にheader.hとsub.cppがある状態です.

|- main.cpp
|- sub
|   |- header.h
|   |- sub.cpp

それぞれのファイルの中身は次のようになります.

// sub/header.h
#ifndef HEADER_H_INCLUDED
#define HEADER_H_INCLUDED

int f();

#endif // HEADER_H_INCLUDED

// sub/sub.cpp
#include "header.h"

int f()
{
    return -1;
}

// main.cpp
#include <iostream>
#include "sub/header.h"

using namespace std;

int main()
{
    cout << f() << endl;
}

これをコンパイルしてみましょう.

cl /EHsc main.cpp sub/sub.cpp

いくつか注目するポイントがあります.

1つ目は,#includeの後の書き方に2種類あることです.一般的に,<と>で囲まれている場合,コンパイラが定義したパスからの相対パスでインクルードするファイルを探し,"で囲まれている場合,コンパイルしているファイルのある場所からの相対パスでインクルードするファイルを探した後に,コンパイラが定義したパスからの相対パスでインクルードするファイルを探します.

2つ目は,sub/header.hという書き方です.1つ目の説明でも書いたように,インクルードするファイルの指定は相対パスになるので,フォルダを指定して,そのフォルダ以下にあるファイルを指定することも可能なのです.

さて,先ほどからコンパイラが定義した場所から,と言っていますが,それを追加する方法があります.まずは,main.cppを次のように書き換えてみましょう.

// main.cpp
#include <iostream>
#include 
using namespace std; int main() { cout << f() << endl; }

次のように,/I subを追加すると,コンパイルできます.

cl /EHsc /I sub main.cpp sub/sub.cpp

/I の後にパスを書くことで,コンパイラがインクルードするファイルを探すパスのリストに追加することができます.

一般的には,他のライブラリを利用する場合,そのライブラリのヘッダファイルなどは一か所にまとまっていて,そのルートフォルダへのパスを/Iで指定することで利用できるようにします.

これは,プロジェクトのプロパティでは,C/C++の全般の追加のインクルードディレクトリに当たります.この項目に ; 区切りで設定したパスは,区切りで分けられ,それぞれ/I に指定されます.

同様の設定は,Xcodeにも存在しますし,Xcodeが利用しているclangというコンパイラにも同様の設定があります.clangの場合 -I で指定します.そう,似ているんですよ.

まとめ

コマンドラインのコンパイラへの理解を深めることで,IDEの設定項目についての理解を深め,プロジェクトの環境を整えることができるようになります.

まだまだ説明したいことはありますが,長くなるのでまた分けて書こうと思います.

考えている話題は,こんな感じです.

  • 警告レベルの変更
  • 警告やエラーの抑制
  • 警告をエラーに
  • 最適化レベルの変更
  • リンク関係の設定
  • ソースコードからコンパイラに指示を出す方法
  • ビルドイベント
  • C++言語仕様の選択

0 件のコメント:

コメントを投稿