C プラグマ

提供:yonewiki
2022年9月26日 (月) 10:40時点におけるYo-net (トーク | 投稿記録)による版 (ページの作成:「C++へ戻る ※このページではC言語にも存在していたという意味で記事タイトルがC プラグマになっていますが、<br /> C++でも同様です。C++だけの機能がある場合は明記します。<br /> <br /> <big><big>'''プラグマ'''</big></big> プラグマは、一般的に処理系独自のコンパイルオプションやコンパイル時の取り…」)
(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)

C++へ戻る


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

プラグマ


プラグマは、一般的に処理系独自のコンパイルオプションやコンパイル時の取り扱いをプログラムソースコード上で制御するためのディレクティブです。このように説明するとなんだか簡単なことを制御するだけのディレクティブに思えますが、VisualStudioでWindowsアプリを作成するようなプログラマにとっては、全てを理解することは非常に難しいことです。なぜなら、Windows独自の技術自体をコンパイルオプションによって制御するということですから、Windows OSの技術の中でも、通常のパソコン操作程度では聞いたこともないような技術について知る必要があることを含め、Visual Studioで設定可能なプロジェクトのプロパティの全てを理解していくのと等しいことだからです。プラグマによって制御できる全てを理解することは非常に難しいことですが、基本的に意識する必要のないコンパイルオプションもふんだんに含まれているので、深く理解をせずに、アプリケーションを作成する上で効率をあげるために理解するべき内容から理解していくことのほうが重要になります。


OS自体も人間には理解しにくい部分はなるべく意識せずにコンピュータを制御することを支援するために、作られているのですから、制御できるすべてを改めてプログラム側で制御する指示をすることを理解するのは、難しくて当たり前です。それでも、制御できる部分があるということは、一般的によく利用されるべき制御部分があることも確かなことでありまして、コンピュータを使って何をする人が多いのかを考えれば、インターネットをする人や文書を作成する人が多いことと同じで、プログラムを作成する際に制御されるべき部分についても、よく制御される部分とあまり制御されない部分とがあります。もちろん、どんなことにも対処できるプログラマになるにはすべてを理解した方がいいに決まっています。どこまで理解するべきかはプログラマ自身が決めることです。


ここでは、よく使われるであろうマイクロソフト固有のプラグマについて触れ、出来る限りの解説を長期間をかけて記載していきたいと思いますが、まずはプラグマによる制御の基本だけを触れて、Windowsアプリの記事を書くまでの間は、基本の部分以外については放置することになります。すべてを理解したい人は、MSDNによる解説とインターネットに散在している解説記事を閲覧して頂ければと思います(自分に言われなくても、勉学に励んでおられる人ならば、そうするにきまってるんでしょう。)。


  • MSDNでのプラグマに関する説明ページ
https://msdn.microsoft.com/ja-jp/library/d9x1s805.aspx

#pragma

#pragmaディレクティブは半角スペースに続けてもう一つのワードと引数パラメータを伴って定義する形式のものがほとんどです。また、マイクロソフト固有の仕様ですが、#pragmaの代わりに__pragmaでも同様の機能として動作するようになっています。これはディレクティブとして先頭に使われる#以降の#はプリプロセッサ演算子の文字列化演算子として扱われることを回避するために用意されています。


#pragma comment

  • lib

#pragma comment(lib, [” ”])

#pragma commentはよく使われるプラグマの中のひとつで、その中でも1つ目の引数のlibと記述するパラメータを渡すものがよく使われていると思います。具体的には


#pragma comment(lib, "拡張子libのタイプライブラリ名")


として利用します。ライブラリの検索と読み込みについての指示を実行ファイル(.objや.oの拡張子がよく使われる)に記述するための指示です。実行ファイルの先頭付近に実行ファイルが必要とするライブラリに関する記述が加えられるようになっています。ASCIIコードで挿入されるためバイナリーファイルを確認すれば、必要としているライブラリが分かります。「/DEFAULTLIB:ライブラリ名.lib」のように記載されます。リンク処理時に実行ファイルに記述されたライブラリ検索と読み込みを実行させることができるため、結果的に予め準備していたタイプライブラリの関数を利用することができるようになります。このような指示はVisualStudioではプロジェクトのプロパティでも同様に指定する手段が設けられています。


また、プロジェクトのプロパティで実行ファイルに記述されたライブラリの検索と読み込みの指示を無視するオプション /nodefaultlib も用意されているため、このオプションの内容に従ってリンクがなされた場合、ライブラリは読み込まれません。もしライブラリを読み込まないでリンク処理を実行するとエラーになるような場合には当然、関数(シンボル)が見つからない旨のエラーが発生しますので、別の方法でライブラリの読み込まないオプションを利用したことを補う必要が発生します。更には /Zl オプションを使うと実行ファイルにライブラリの検索と読み込みの指示についての書き出しがされないようにすることもできます。つまり、#pragma comment(lib, "xxxxx")の指示が無視されます。/Zlオプションは不要意にライブラリを読み込んで実行ファイルが肥大化するのを防ぎ、スペースを節約する役割のモノです。ライブラリの読み込み順序や読み込まれる部分を思慮深く、指定することを制御することを実現するための仕組みです。このあたりのオプション関連の動作も含めて一連の処理を理解しようとすると非常に複雑なものになります。このような問題からライブラリが見つからない場合、実行ファイルからリンク処理をするときには関数名が若干、修飾変換された関数名になっているため、少し変化した関数名で関数が見つかりませんというエラーが表示されます。こういった表示が出る場合は、これらのライブラリ読み込みに関するオプションやpragmaでの制御がうまくいかずにリンク処理が失敗していることを意味しますので、何が欠けているのか?その関数は何と言うライブラリで提供されていて、どこで読み込みを指示しているか?どのタイミングで読み込まれるか?きちんと読み込まれるかまでを再検討する必要が発生します。人からもらったプロジェクトをそのままコンパイルしてうまく動かない場合は、だいたいこのあたりの記述で問題が起こっていたり、環境変数LIBへのパス設定不足やライブラリの不足あるいはライブラリパスの変更による問題が発生していることがほとんどです。


プロジェクトファイルを配布しているにも関わらず、受け取ったファイルがそのまま動かせなかったり、プロジェクトの展開方法についての指示がないというものは、プロジェクト配布者に落ち度があると考えてよいですが、それが、意図的なのか、能力不足なのかは知る由はありません。プロジェクトファイルを公開しようとしているスタンスだけを評価し、プロジェクト配布者の能力や人柄を判断することになります。自分の場合はプロジェクトの中身を精査して、ライブラリパスに個人的なフォルダ設定を残したまま配布してる場合は、こういうものにはあまり関わりたくないと感じるほうですね。プロジェクトを完成させたくらいだから能力はあるんだろうけど、こんな状態で配布してるプロジェクトと関わると最終的には、どうせろくなことにならないと、そのように感じてしまいます。それが意図的であるなら尚更です。


このサイトを含め、ただで勉強しようとすると、結局は時間の無駄に終わることはよくあります。学ぶべき手段を選び間違えた自分を悔いるしかないです。そうならないように、できるだけ有用なものにはしていきたいとは思いますが、違う生業(なりわい)のかたわらの執筆です。なにぶんお聞き苦しい点も多々あろうかとは存じますが、耳を傾けていただければと思います。


文字列操作について触れた記事で、ICUのライブラリを使った記事があります。そこでは具体的には、以下のように利用しました。

//ICU ucnvプリプロセッサ(ライブラリはデバッグモードとリリースモード個別設定)
#include <unicode/ucnv.h>     //ucnv文字コード変換ライブラリヘッダ
#include <unicode/translit.h> //文字変換ライブラリヘッダ
#include <unicode/regex.h>    //正規表現
#include <unicode/ucsdet.h>   //文字コード判定
#include <unicode/ucal.h>   //文字コード判定
#include <unicode/uclean.h> //u_cleanup関数
/* for uloc_getDefault() */
#include <unicode/uloc.h>
#include <unicode/calendar.h>
#include <unicode/smpdtfmt.h>

//#include "unicode/utypes.h"
//#include "unicode/locid.h"
//#include "unicode/unistr.h"
//#include "unicode/tzfmt.h"
//#include "unicode/tznames.h"


#ifdef _DEBUG
#pragma comment(lib, "icudt.lib")
#pragma comment(lib, "icuucd.lib")  //ICU ucnvを使うために必要なライブラリ
#pragma comment(lib, "icuind.lib")  //ICU Transliterate関数を使うために必要なライブラリ 正規表現関数も
#pragma comment(lib, "iculed.lib")
#pragma comment(lib, "iculxd.lib")  
#pragma comment(lib, "icuiod.lib")  
#pragma comment(lib, "icutud.lib")  

#else
#pragma comment(lib, "icudt.lib")
#pragma comment(lib, "icuin.lib")  //ICU Transliterate関数を使うために必要なライブラリ 正規表現関数も
#pragma comment(lib, "icuuc.lib")  //ICU ucnvを使うために必要なライブラリ
#pragma comment(lib, "icuio.lib")
#pragma comment(lib, "icule.lib")
#pragma comment(lib, "iculx.lib")    
#pragma comment(lib, "icutu.lib")  
#endif

//Data   Library                     icudtXX(d).dll icudt(d).lib
//Common Library                     icuucXX(d).dll icuuc(d).lib
//Internationalization(i18n) Library icuinXX(d).dll icuin(d).lib
//Layout Engine                      iculeXX(d).dll icule(d).lib
//Layout Extention Engine            iculxXX(d).dll iculx(d).lib
//ICU I/O(Unicode stdio) Library     icuioXX(d).dll icuio(d).lib
//Tool Utility Library               icutuXX(d).dll icutu(d).lib

//ICU ucnv _End

#include "UnicodeConverter.h"

20行目から37行目のようにプラグマと#ifディレクティブのようなプリプロセッサ構文制御やマクロ定義といったディレクティブを併用して、適切なライブラリがロードされるようにプログラムソースで記述することができます。_DEBUGというマクロ定義はVisualStudioではプロジェクトの中でもデバッグモードというプログラム作成時の不具合箇所発見をするための方式を利用している作業のときだけ定義されるマクロで、要するにモード毎に異なるライブラリを使っています。ライブラリにもデバッグ向けのライブラリとリリース向けのライブラリが分かれている場合があり、デバッグ向けのライブラリを使っているとデバッグをしやすいように例外処理による警告が発動するように作られていて、ライブラリ利用中に発生した問題でも、どのような問題が起こったのかをわかりやすく支援してくれる形式になっています。


もちろん、プロジェクトのプロパティもリリースモード、デバッグモードで別々に設定することができますので、個別にプロパティ部分の利用するライブラリ名を記述する欄に記述することができます。


この他にも、#pragma comment()のディレクティブには第一引数に指定できる値があり、

  • compiler
  • exestr もう使われなくなる予定なので説明を省略。
  • linker
  • user

があります。それぞれについても以下に説明します。


 

#pragma comment(compiler)

使い方は、決まっていて、以下の通りで、第二パラメータはありません。


#pragma comment(compiler)


実行ファイル(.objファイル)にコンパイラのバージョン情報が記述されます。#pragma comment(user,[" "])と#pragma comment(comiler,[" "])は末尾にコンパイル処理ファイル順、記述行での登場順で記述されます。


Visual Studioのリンク処理では実行ファイル(.obj)に記述されているコンパイラのバージョンに不一致があればエラーとなります。したがって、2つ以上のヘッダファイルがあるプロジェクトで、一度ビルドしたあと、このプラグマを一つのヘッダファイルだけに記述して、従来利用していたヘッダファイルをコメント化するなどして無効化した状態でビルドした場合、objファイルの中のコンパイラバージョンの記述に不一致が発生し、エラーが発生します。リビルドするとこの問題は解決します。逆にある一つのヘッダファイルだけにこのプラグマがある状態で、一度ビルドしたプロジェクトから、このプラグマを消すと同じようにエラーが発生するケースもあります。この場合もリビルドすることによってコンパイラバージョン情報に関する整合性エラーが解決します。


自分は現在も使っている開発環境としてVisual Studio Proffesionalの2010~2013までの各バージョンのライセンスを持っていますが、VisualStudio2012をメインに使っています。現在は使っていない古いバージョンでは、何を血迷ったか個人的に18万円ほど支払ってEnterprise Editionを使っていた無知な時期もあります。何か更新がある耽美にマイクロソフトから大量にCDが送られてきて、なんじゃこりゃ!的な状態に陥っていました。全部のWindows OSのバージョンのCDとか、MSDN(英語があまり読めないのにもちろん英語版)の変更があるだけでもCDが来るし…。何が何であれ、個人的にEnterprise Editionとか買うとかってのは間違っているなと思いました。なんでもいっちゃんええのが勉強になるって思ってましたが、どんなものにも身分相応ってのがあるんだということをこのときはじめて理解しました。話は戻りますが、試しにこのプラグマを使ってみたらobjファイルの末尾に


Microsoft (R) Optimizing Compiler Version 17.00.61030.0


となるような、ASCIIコードが挿入されました。.objファイルはバイナリーファイルですので、ASCIIコードとして読み取れる部分とそうでない部分とが混在するファイルになっています。


 

#pragma comment(linker, [” ”])

2番目の引数の二重引用符内の先頭に決められたオプション文字列を記述することで、リンクオプションを追加できる指示になります。


  • #pragma comment(linker, ”/INCLUDE:[]”)
シンボル参照の強制と同じ動作で、シンボル(関数名)を記述するとシンボルテーブルに追加されます。これはプログラム中で使われないような関数をリンカが無視するのを防いで、実行可能ファイルからとりこむような処理です。


  • #pragma comment(linker, ”/DEFAULTLIB:[]”)
リンク処理時のライブラリの参照を指定します。優先度はコマンドライン→/DEFAULTLIB→実行可能ファイル(.obj)内に記述されたライブラリ参照の順で参照され、/NODEFAULTLIBによるライブラリ参照の無視に関する指示が優先されます。/NODEFAULTLIB関連の指示が優先されることから、これらのライブラリ参照の指示によらず、外部に依存していないリンク処理になっていることを確実にすることができます。


  • #pragma comment(linker, ”/EXPORT:[]”)
プログラムから関数を出力して、他のファイルから関数を利用できるようにするものです。
実際には
#pragma comment(linker, ”/EXPORT:関数名(@1~65536の値(省略可),NONAME(省略可),DATA(省略可))”)
という具合に利用します。1~65536の値は関数に割り振られる番号で省略すると、リンク処理時に自動で割り付けられます。NONAMEと指定すると関数名を省略した1~65535の番号によって呼び出す関数となります。DATAを使うとデータとして取り扱われます。
関数の出力はプログラムソースに__declspec(dllexport)キーワードを付けて出力する方法や.defファイルにEXPORT命令を使う方法もあります。なんだか意味不明な説明に感じると思いますが、ここではDLLのような外部から呼び出される関数やデータを作成する場合に使うプラグマがあると覚えておけばよいと思います。詳細は別の項目で記述するつもりです。


  • #pragma comment(linker, ”\"/MANIFESTDEPENDENCY:[]\"”)
マニフェストの依存関係についての記述をします。マニフェストとは声明文のような意味を持つイタリア語ですが、マイクロソフトではこのまにゅフェストと呼ばれる仕組みでClickOnceアプリケーションと呼ばれる自己更新型のアプリケーションを作成するための技術として用意されているものです。マニフェストはXMLによる構文でいくつかの要素を記述することになっていますが、このMANIFESTDEPENDENCYという要素にはアプリケーションのバージョンに関する情報を記述することになっています。詳細な情報については、ここで簡単に説明できるようなものではないので、これも別の場所で説明したいと思います。


  • #pragma comment(linker, ”/SECTION:[]”)
リンク処理ではセクションと呼ばれる単位で関数が配置されます。VxD(Virtual x device Driver:仮想デバイスドライバ OSと同じレベルの権限をもつ制御プログラム)の開発では、このようなセクションで分割される実行ファイルの配置を制御してメモリの使い方を厳密に指定する必要がある場合があります。SECTIONでは以下のよう定義することで、セクションの属性を変更することができます。
#pragma comment(linker, ”/SECTION:セクション名,(!)セクション属性記号,ALIGN=#2の累乗で表現される値(省略可)”)
このような動作を理解するにはリンク処理がどのようなものなのかを理解する必要がありますので、理解を深めるにはリンク処理を何度も実行して、セクションの生成のされかたや、デフォルトで定義されるセクションとROM配置やRAM配置の法則を知っていく必要があります。もしく同様の解説をしてくれる文献やサイトに触れる必要があります。ここでは説明しきれませんし、いまのところ自分もそれほど詳しくはありません。ROMやRAMの配置まで意識する必要があるときに使うものだと知っていれば十分だと思います。自分自身も理解を進めた場合には追加の記事を作成したいと思います。


  • #pragma comment(linker, ”/MERGE:[]”)
上記のセクションに関する指示と同様にリンク処理よって生成されるセクションを統合させる処理です。例えば、以下のように使います。
#pragma comment(linker, ”/MERGE:統合するセクション名1=統合するセクション名2(統合後のセクション名でもある)”)
とします。


リンク処理の解析は機械語を読み解くのと同様に複雑ですが、動作自体は簡単な処理だと言われています。なんだかよくわからない処理がなされているように思われがちなリンク処理ですが、法則を理解出来れば、もっと理解が深まるということだと思います。

 

#pragma comment(user, [” ”])

実行ファイル(.obj)に、実行可能ファイル生成に影響を及ぼさないコメントを記述することができます。objファイルはバイナリーファイルなので、ASCIIコードの範囲にあるような1バイトの英文字を使うことをお勧めします。#pragma comment(user,[" "])と#pragma comment(comiler,[" "])は末尾にコンパイル処理ファイル順、記述行での登場順で記述されます。


以下のように記述して利用します。


#pragma comment(user, ”コメント(ASCIIコード範囲の文字利用を推奨)”)


 

#pragma once

#includeのようなソース読み込み指示によって、ソースファイルが重複してオープンされないように制御するためのプラグマです。プロジェクトに使われるヘッダファイルとメインソースコードファイルの全てに追加しておくべきプラグマです。以下のマクロと同等だと考えてよいです。

FILE sample.h
#ifndef __SAMPLE.H__
#define __SAMPLE.H__

ヘッダファイルプロトタイプ宣言

#endif

上記のようなマクロでは、インクルードガードと呼ばれる手法を使っていると呼ばれていて、一度ヘッダファイルを読み込むとファイル名ごとに決められたマクロ名が定義され、2回目にヘッダファイルが呼び出された時にはマクロが定義されているため、#ifndefの条件が成立せず、重複して読み込まれないようになります。


上記のようなインクルードガードを簡潔にするのが、#pragma onceです。処理系によってこのプラグマは使えないものもありますが、最近のコンパイラでは広く適用できるプラグマになっているので、汎用性が高まると思います。


上記マクロを以下のようにプラグマを使って、すっきりと記述できます。

FILE sample.h
#pragma once
ヘッダファイルプロトタイプ宣言


 但し、この方法は標準化されたものではないため、Microsoft Visual Studio C++ 4.0以上でしか動作しないと考えて扱っても良いのかもしれません。他のコンパイラでも動作するものもありますが、自分の環境に合わせてしまっても良いと思います。他の人に配るなら、

FILE sample.h
#ifndef __SAMPLE.H__
#define __SAMPLE.H__

#if _MSC_VER > 1000
#pragma once
#endif
ヘッダファイルプロトタイプ宣言

#endif


のように記述すると良いと思います。インクルードガードによる手法をサポートしつつも、#pragma onceの機能も有効利用できます。コンパイル速度が#pragma onceの方が早いので、おすすめです。他のコンパイラでも速度が速くなるようにインクルードガードを実現するには、冗長インクルードガードと呼ばれる手法を使います

#ifndef __SAMPLE.H__
#include "sample.h"
#endif

のようにしてインクルードガード下上でさらにインクルード分のところでも読み込むかどうかを判断する処理を加えます。インクルード部がかなり煩雑になりますが、2重に読み込むことをより早い段階で避けることが出来るので、コンパイル速度の向上が見込めます。


C++へ戻る