C ディレクティブ

提供:yonewiki
2022年9月26日 (月) 10:38時点におけるYo-net (トーク | 投稿記録)による版 (ページの作成:「C++へ戻る ※このページではC言語にも存在していたという意味で記事タイトルがC ディレクティブになっていますが、<br /> C++でも同様です。C++だけの機能がある場合は明記します。<br /> <br /> <big><big>'''ディレクティブ'''</big></big> ディレクティブとは日本語で指示するという意味でDirectiveは映画や…」)
(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)

C++へ戻る


※このページではC言語にも存在していたという意味で記事タイトルがC ディレクティブになっていますが、
C++でも同様です。C++だけの機能がある場合は明記します。

ディレクティブ


ディレクティブとは日本語で指示するという意味でDirectiveは映画やドラマのDirectorでも同じような意味で制作指揮者・監督というような意味で使われます。プリプロセスを指示するための命令文がディレクティブであることについてはC プリプロセッサの項目で記述しました。


ディレクティブは主に#記号からはじまる命令文で記述され、プログラムのどこの部分でも利用されることもありますが、基本的にはプログラムコードを記述したテキストの先頭に集中して記載されることが多いです。プログラムの途中でよく使われるディレクティブも存在します。


では、どういったディレクティブがあるのかを、以下に一覧します。既によく知っているものから、あまり使わないものまであるかもしれませんが、必要な範囲で必要なだけここにそれぞれのディレクティブの意味や使い方を記述します。





  • #define
  • #undef
詳細はC マクロに記述
  • #pragma
詳細はC プラグマに記述


#include

#includeディレクティブでは、インクルードファイルを検索して読み込む処理を記述します。以下のような2とおりの記述ができます。

#include ”(パス\)インクルードファイル名”

#include <(パス\)インクルードファイル名>


" "2重引用符と< >山カッコ(小なり 大なり)の2とおりのファイルパス記述が#includeの命令の後ろに空白を挟んで記述することで指定できますが、それぞれはディレクトリの検索順序が異なります。" "2重引用符はプロジェクト固有のインクルードファイルを指定し、< >山カッコは標準関数やSDKなどの開発環境設定を優先するようなインクルードファイルを指定するために利用されます。


具体的には以下の優先順序になる。



” ”2重引用符

  1. #includeディレクティブが記述されたファイルの置かれているディレクトリ内、相対パスがある場合はファイルの置かれているディレクトリからの相対パス内。相対パスに関する扱いは以降も同じなので省略。
  2. #includeディレクティブによって最後に開かれたファイルが優先されて使われ、ディレクトリの検索は読み込みをしたファイルが置かれている親ディレクトリ。なければ更に読み込みをしたファイルが更に#includeディレクティブによって読み込まれていたなら、読み込みをした親ディレクトリ(親の親)と遡っていく。
  3. /I コンパイルオプションで指定されているディレクトリ
  4. 環境変数INCLUDEで指定されたディレクトリ


< >山カッコ

  1. /I コンパイルオプションで指定されているディレクトリ
  2. 環境変数INCLUDEで指定されたディレクトリ


 よく使われていたインクルードの宣言は#include <stdio.h>で、C++11という標準規格が制定された2011年くらいまでは、この方法でした。C++11以降はstdioに変わる新しいヘッダファイルcstdioに置き換わっています。VisualStudio2012あたりからcstdioが使われ始めています。他にも主要なヘッダファイルは先頭にcがついて、後ろの.hがないヘッダファイルに置き換わっています。VisualStudio2019ではC++20までの標準規格規格に対応しつつあり、メニュー{デバッグ}>{(プロジェクト名)のデバッグ}で表示されるダイアログの左側ツリーから全般を選択したときに表示される{言語の標準規格の設定}でどの規格を使うかが設定できます。既定ではC++14になっています。C++14の設定でもC++17やC++20の一部の機能は使えます。つまり、VS2019ではもうとっくにC++11準拠なので、cstdioを使った方が良いという感じです。他のコンパイラでもそろそろ、cstdioが対応されていると思います。


標準ヘッダファイルが新しくなったモノ一覧

  • <cassert>
  • <ccomplex>
  • <cctype>
  • <cerrno>
  • <cfenv>
  • <cfloat>
  • <cinttypes>
  • <ciso646>
  • <climits>
  • <clocale>
  • <cmath>
  • <csetjmp>
  • <csignal>
  • <cstdalign>
  • <cstdarg>
  • <cstdbool>
  • <cstddef>
  • <cstdint>
  • <cstdio>
  • <cstdlib>
  • <cstring>
  • <ctgmath>
  • <ctime>
  • <cuchar>
  • <cwchar>
  • <cwctype>


 incluedeファイルの入れ子は10レベルまでとなっているので、あまり階層(呼び出しの呼び出しの呼び出し…のような感じ)が深くならないようにディレクティブによるヘッダの呼び出しをするように組み込みましょう。絶対パスを指定した場合はその他のディレクトリは検索しないことになっていますので、次々と検索されるのを防ぐ必要がある場合には絶対パスが利用できます。但し、絶対パスにした場合、第三者にプログラムを再利用してもらう場合の配慮が必要になりますので、用途に応じた対応が必要になります。あえて絶対パスにして、自分以外のPCでは簡単にコンパイルされないようにする嫌がらせも可能です。原因がわからない人には使えないという試練を与えるドラマでいうところのなんだその変な胡散臭い試練はって奴になります。


マイクロソフト固有の処理としては、環境変数や/I オプションには;区切りで複数のパスを指定できるが、先頭に記述されているパスが優先されるというものです。


インクルードファイル名が重複する場合には、このあたりの優先度を理解してインクルードパスの指定をきっちりとしてやることが大事になってきます。

#using

C++/CLIで使われる共通言語基盤のライブラリを読み込む場合に利用するものですので、説明を省略します。C++/CLIの項目で解説するかもしれません。


 

#import

タイプライブラリ(.olb/.tlb/.dll/.ocx/.exe…)の読み込みを指示するディレクティブです。C++では、読み込んだタイプライブラリはC++のクラスとして展開されます。


利用方法は#includeによく似ています。


#import ”(パス\)タイプライブラリファイル名” (属性)

#import <(パス\)タイプライブラリファイル名> (属性)


のように指定します。タイプライブラリ名を指定した後ろに空白を設けて(属性)とあるように、さまざまなオプションとなるような属性を指定することもできます。属性についてはWinアプリの分散モジュールのようなタイプライブラリを使っている分にはマイクロソフト固有のものしか付与しないと思います。詳細はMSDNの https://msdn.microsoft.com/ja-jp/library/298h7faa.aspx を参照して下さい。ファイルの検索の優先度は以下のとおりです。#includeによく似ていますが違っている部分もあるので、勘違いしないようにしましょう。COM/COM+オブジェクトではこのようなタイプライブラリに一意に決まるIDを付与しています。そのようなIDを利用する手法として(パス\)タイプライブラリファイル名の代わりにprogid:サフィックスではじまるタイプライブラリのコントロールのprogidを記述する手法として、progid:ライブラリ名.クラス名のような形式やlibid:サフィックスではじまるタイプライブラリのライブラリIDを記述する手法として、libid:2DF8D04C-5BFA-101B-BDE5-00AA0044DE52のような形式を使うことも出来ます。このようなタイプライブラリの使い方はマイクロソフトやその他企業が提供するものは巨大で複雑なものが多くここで簡単に説明できるような内容ではないためWinアプリの記事を書くときがあれば、記述しますが、ここではディレクティブの使い方だけを取り上げているため説明を省略します。


*一意に決まるIDはGUID(Global Unique Identifer)と呼ばれています。GUID生成アルゴリズムによって作成されたGUIDは西暦5770年まで、1秒間に1000万個のIDを発番しても重複しないIDとして保障されているため個人が発番しても、有名企業が発番しても重複しないIDになるため自由に生成アプリを使って発番できるようになっています。もちろん誰かが発番したIDを横取りすれば重複してしまうので、横取りをすることは何も良いことはありません。プログラムが併用できなくなるため、横取りされたプログラムの関係者やプログラムのユーザに迷惑をかけるだけの結果になります。

  • CLSID クラス識別子
  • DISPID ディスパッチ識別子
  • IID インターフェース識別子
  • ProgID(short for PROGrammatic IDentifier)日本語対応表現は不明もしくは存在しない。


OLEview.exeでタイプライブラリの情報を閲覧できます。

OLEview.exeはC:\Program Files (x86)\Windows Kits\8.0\bin\x64にあります(VisualStudio2012の場合 ※インストールディレクトリによって異なる場合があります。)。


guidgen.exeでGUIDの生成が出来ます。

guidgen.exeはC:\Program Files (x86)\Microsoft Visual Studio 11.0\Common7\Toolsにあります(VisualStudio2012の場合 ※インストールディレクトリによって異なる場合があります。)。


” ”2重引用符

  1. #importディレクティブが記述されたファイルの置かれているディレクトリ内、相対パスがある場合はファイルの置かれているディレクトリからの相対パス内。相対パスに関する扱いは以降も同じなので省略。
  2. #includeディレクティブによって最後に開かれたファイルが優先されて使われ、ディレクトリの検索は読み込みをしたファイルが置かれている親ディレクトリ。なければ更に読み込みをしたファイルが更に#includeディレクティブによって読み込まれていたなら、読み込みをした親ディレクトリ(親の親)と遡っていく。
  3. /I コンパイルオプションで指定されているディレクトリ
  4. 環境変数INCLUDEで指定されたディレクトリ


< >山カッコ

  1. 環境変数PATHで指定されたディレクトリ
  2. 環境変数LIBで指定されたディレクトリ
  3. /I コンパイルオプションで指定されているディレクトリ、指定の無い場合は他の#import **** no_registryの属性をもった形式によって読み込まれたタイプライブラリが参照するライブラリを検索。※no_registry属性はタイプライブラリのレジストリを検索しないことを宣言する属性。


使用例 記述例ですので、以下のディレクティブが実際に使えるかどうかは保証しません。

// XML
#import "msxml6.dll" named_guids raw_interfaces_only
#import "progid:msxml2.domdocument" raw_interfaces_only auto_rename

// Office
#import "libid:2DF8D04C-5BFA-101B-BDE5-00AA0044DE52" version("2.4") lcid("0") raw_interfaces_only auto_rename

// Word
#import "libid:00020813-0000-0000-C000-000000000046" version("1.6") lcid("0") raw_interfaces_only auto_rename


 

#line

__LINE__と__FILE__マクロの値を書き換えるディレクティブです。コンパイルエラー処理の際に表示するファイル名や行番号を入れ替えて、コンパイルエラーの根本の原因箇所が異なることを明示的に示すことに利用します。


 

#error

ディレクディブの後ろに半角スペースのトークンを挟んで、エラーメッセージを記述します。ただし、日本語SJISコードでプログラムを記述している場合には日本語コードの中の5Cが使われる文字がある場合、1行のディレクティブを複数の行で記述するための改行文字であると処理しようとするため、例えば、「表示」のような記述をする場合は、表\示として、改行の打消しをする必要があります。改行を宣言する \ をつかった後ろに文字コードがあるのはディレクティブ記述の違反となります。


#errorディレクティブは以下のような形式になります。


#error エラーメッセージ


仕様例としては、ディレクティブ処理におけるエラーになる何らかの条件を満たしたときにエラーを吐き出すために以下のような方法で利用されます。

#if *****
#error xxx.libタイプライブラリが読み込まれませんでした。
#endif

 

#if/#elif/#else/#endif

プリプロセッサの制御用構文です。プログラム内のif文とよく似ている制御構造を持つことが出来ますが、比較演算子に使われる変数はマクロとよばれる変数によってあらかじめ定義されていなければ、うまく比較することができないかったり、プログラム内と同等の複雑な比較演算はできないことが異なります。#ifはif、#elifはelse if、#elseはelseによく似ている働きに相当しますが、{ }による制御実行範囲の指定がないため、 制御構造の終端に#endifと記述する必要があり、#if~#endifの間に記述されているないようがプリプロセッサの制御構造となります。制御構造内にあれば#elifは何度でも利用できて、#else~#endifはどれにもあてはまらない場合の処理区間となます。#elifと#elseは必要なければ、省略することが可能です。


プリプロセッサ演算子のdefined()を比較演算子の結果として利用することができるため

#define __MYDIRECTIVE_MACRO__




#if defined(__MYDIRECTIVE_MACRO__) 
    f = 1;
#else
    f = 0;
#endif

のように__MYDIRECTIVE_MACRO__というマクロが定義されているかどうかを判定するプリプロセッサ演算子を比較演算子に使うことができます。そのほかの比較演算子としては >=、<=、>、<、==、!= が利用できます。#ifディレクディブの後に続く部分に条件を記述しますが、条件としての比較演算子の演算結果が0なら偽:falseであり、それ以外なら真:trueとなります。条件は比較演算子以外にもマクロの値そのものでも良いです。 #if defined(__MYDIRECTIVE_MACRO__)を#ifdef __MYDIRECTIVE_MACRO__と書き換えることのできる#ifdefディレクティブや#ifndefディレクティブも存在します。


この項目の記事はマクロについて理解していないと、わからないと思いますので、さらにマクロについての理解を深めることをお勧めします。

#ifdef/#ifndef

#ifdefディレクティブや#ifndefディレクティブは#ifディレクティブの利用方法の一部の記述を簡略化するためのディレクティブです。

#ifdef __MYDIRECTIVE_MACRO__
~
#endif

と記述した場合


#if defined(__MYDIRECTIVE_MACRO__)
~
#endif

と同等の記述をしたことになります。#ifディレクティブやdefined()ディレクティブ演算子の動作内容については#ifの説明をした項目を参照して下さい。


#ifndefはif nof defineを省略した意味を持っていて、

#ifndef __MYDIRECTIVE_MACRO__
~
#endif

と記述した場合

#if defined(__MYDIRECTIVE_MACRO__)
#else
~
#endif


という具合の#elseディレクティブの範囲の事象だけ実行する範囲として制御できます。もちろん、#ifndefの後に#elseを使うことは出来て、マクロが定義されている場合の制御構文についても記載できます。

#ifndef __MYDIRECTIVE_MACRO__

~  ※__MYDIRECTIVE_MACRO__ というマクロが定義されていない場合の処理

#else

~  ※__MYDIRECTIVE_MACRO__ というマクロが定義されている場合の処理

#endif


#ifディレクティブの使い方とdefinedディレクティブ演算子とマクロの使い方を理解していないとこの項目は理解できないと思います。


#define/#undef

詳細な説明は上記の個別記事の項目を参照して下さい。

#pragma

詳細な説明は上記の個別記事の項目を参照して下さい。


C++へ戻る