C/C++ で配列の内容を関数として実行する.

OSなし環境とかRTOS環境下ではちょくちょくやってましたが、Linux とかではそういえばやったことないなと思い、やってみました.

Segmantation Fault を出しながら、試行錯誤して一応できたのが以下.

※ (1)
mprotect で指定するアドレスは page size 境界にある必要があるので、そのための attribute.

※ (2)
関数のバイナリを含む配列を .text 領域に配置するための attribute. 最初 .bss セクションにおいてやってみたけれど、どうも .bss だと mprotect で PROT_EXEC を付与してもSegmantation Fault が出るのようなので指定.  なんで出来ないのか… linker script とか Intel のアーキテクチャマニュアルとか、詳解 Linux kernel とか、そのへん読み解いたら分かるかもしれませんが、とりあえず今回は .text に配置.

※ (3)
.text 領域はデフォルトで書き込み権限がないため、mprotect で書き込み権限を付与.

※ (4)
配列内に機械語でバイナリーを直接プログラミングするのがしんどかったので、double の乗算を行う関数 mul を buf にコピー (サイズに指定している 128 は適当で多分余分なのもコピーされている)  今回は単純な乗算なので問題ないけれど、相対アドレッシングで外に分岐したり外のデータにアクセスするような関数の機械語をコピーしても変な所にアクセスして期待した動作はしないのでその辺りは注意.

※(5)
buf の先頭を関数ポインター (double(*)(double, doble)) にキャスト.

※(6)
関数ポインタ fp 経由で buf の内容をコードとして実行.

さてこのソースを make してみると…

g++  -g -Wall -Wextra -c main.cpp
/tmp/ccxKSKnq.s: Assembler messages:
/tmp/ccxKSKnq.s:35: Warning: ignoring changed section attributes for .text
g++ main.o -o main

のようになにやら  warning がでていますが、実行した結果は以下のようになっており、buf は  .text 領域に配置され、コピーされた mul の内容も関数として正しく動作しているようです.

./main
0x402000
0x403000
6.000000
広告

Golang で C++ のクラスを bind する.

C++ で書かれた資産を別言語から利用したというケースは結構あると思いますが、C++ で書かれたコードはシンボルへの名前修飾などの問題があり(問題はそれだけでもという訳ではないけれど)、他言語から直接 C++ のコードを呼び出すというのは簡単には出来ないのが一般的です.

マングリングされたシンボル名を直接指定するとかの力技は置いておいて、この場合一般的にどうするかと言うと実現したい機能の入り口となる C linkage の関数(簡単に言うと C++ で extern “C” をつけた関数)をインターフェースとして用意し、実装ファイルの方で C++ の関数なり、クラスなどを組み合わせて利用して所望の機能を実装するという事が多いと思います.

イメージとしては GoF のファサードパターンのような感じです.

ただこの方法だと裏側の C++ のコード(特にクラス)が持っているインターフェースの詳細が隠蔽されてしまうので、C++のものをそのまま移植したような、整合性のあるインターフェースを持ったクラスを他言語でも利用したいといったケースには対応することができません.

ではこういう場合どうするか.  ポイントは間に横たわる C言語の障壁を突破して如何に以下の 2 点を実現するかにあります.

(1)  Cの障壁を跨いでクラスのインスタンスの情報をどうやり取りするか.
(2) メンバー関数の引数や戻り値に含まれる non-POD (C言語で扱えない)なデータをどうやり取りするか.

これらの具体的な対処法ですが、(1) については C 標準ライブラリのファイルディスクリプタのようにコンテキスト(この場合は bind したいクラスの this の情報) をコンテキストの生成を行う関数から返却し、それを外部で引き回して他の関数に渡して使用するという方法で実現することができます.  また、(2) については bind したいクラスと同じ名前の関数を持ち、かつ入出力が POD オンリーな wrapper クラスを作成し、wrapper から元のクラスに処理を移譲するようにすることで実現する事ができます.  (もし入出力が POD に変換し辛いクラスの場合、そのクラスを先に bind してそのコンテキストを入出力として扱う事で同じく対応する事が可能)

言葉だけの説明ではアレですのでとりあえず以下を御覧ください. (Foo という std::pair<std::string, std::string> の setter / getter を持つ単純なクラスを cgo で bind してみた例です)

https://github.com/cutlassfish/cgo_bind

・Foo
bind 対象のクラス.

・FooWrapper
Foo を wrap し、入出力を POD 型へ(ここでは std::pair<std::string, std::string> を const char* をメンバーに持つ pair 構造体へ)変換するクラス.

・foo_c
FooWrapper の生成および破棄と、呼び出し元から渡されたコンテキストの情報を元に 適切な FooWrapper のインスタンスに呼び出しを移譲する C linkage のインターフェース.

ここでは  FooWrapper のオブジェクトが配置されている仮想メモリー空間のアドレスをコンテキスト(厳密に言うとコンテキスト識別子)として Foo_construct() から返却しています.  (生存期間にあるオブジェクトのアドレスは一意である事が保証されているため、自前で識別子を用意せずとも new から返却されたアドレス自体を識別子として利用することができます)  呼び出し時は go 側から渡された数値としてのアドレスを foo_c 内の関数で FooWrapper 型のポインターにキャストし、FooWrapper 経由で元の Foo へ処理を移譲しています.

このような単純な例だと FooWrapper のような物を用意するのは冗長に思えるかもしれませんが、bind 対象のクラスのメンバー関数が増えてくると POD <–> non POD の変換に複数の状態が絡んでくる事になるので、よほど単純な場合以外はメンテナンス性の観点から wrapper を用意して局所化を行っておくのが良いと思います .

今回  golang へ bind と言うことでやってみましたが、基本的な考え方は他の言語で bind する場合にも応用できるはずです.

template クラスを private メンバーに含むクラスを DLL 化した時に出る C4251 warning について.

template クラスを private メンバーに含むクラスを DLL 化した際に「class ‘XXXXXX’ は __export キーワードを使って class ‘YYYYYY’ にエクスポートしてください」のような warning に出くわした方は多いと思います.

何故、template の export をしろと言われてしまうのか… 公式のドキュメントにもその理由についてははっきり書かれてはいないのであくまで推測になってしまいますが、このケース(正確には template メンバーが private、 inline での使用なし、メンバー関数の入出力に関係なし)に限ってはMSVC の処理系の都合で warning が出ていると思われます.

そもそも template のインスタンスの export が必要なのは「template のコードは翻訳単位ごとにインスタンス化される」と言う処理系の template サポートの方法と、実行時までどのようなコードがリンクされるか分からないと言う DLL の性質によるものと思われます. (MSVCがどのような実装になっているかは定かではないですが、恐らく例外ではないかと…  詳細は本の虫を参照)

仮に export を行わなかった場合、DLL 側が保持している template のインスタンスとクライアント側の template のインスタンスがシンボル的には一致しているにも関わらず処理内容が異なると言う事がありえますし、実際にそのような仕様になっているかは別として、DLL とクライアント側でコンパイルに使用された処理系のバージョンやオプションが異なる場合、元となった template の記述が同一であったとしても想定しているデータアライメント等が異なると言うことも起こり得ます.

一方クラスの template メンバー変数が private で inline でも使用されておらず、メンバー関数の入出力とも無縁な場合、基本的にはクライアント側でコードのインスタンス化は必要ないはずですが実際には C4251 warning が出てしまいます…   恐らく処理系の都合なのでしょうが、なぜこういったケースを検出できないのか??  template の仕様を逆手に取った private メンバーへのアクセス等まで考慮した結果なのか???

前置きが長くなってしまいました.

さて、この warning への対処方法ですが、メッセージ通りに template class のインスタンスを DLL から export する方法と合わせて代表的には以下の 3 つがあるのではないかと.

(1) #pragma で C4251 を無視する.
(2) メンバー変数として保持している template の instance を全て export する.
(3) pimpl イディオムを利用する.

(1) #pragma で C4251 を無視する.

もっとも楽な方法ですが、C4251 で警告されるのはこの template 絡みの warning のみではないようなので、DLL の危険性に関する知識がない場合 data crruption に繋がる warning を見逃してしまうかもしれません.  そうでなくても #pragma による warning 抑制はその意味と危険性を理解した上で行う最終手段的な側面があるため、むやみに使用せず他の手がないかまず考えるべきだと思います.

https://docs.microsoft.com/ja-jp/cpp/error-messages/compiler-warnings/compiler-warning-level-1-c4251

(2) メンバー変数として保持している template の instance を全て export

__declspec(dllexport) を使用してメンバー変数として保持している template のインスタンスを export する方法です.  自前で定義した単純な template の場合は問題ありませんが、STL のような複雑なものを export する場合、デフォルト指定されている template 変数 (STL コンテナのアロケーター等)や基底クラスまで遡って全てインスタンス化を行う必要があるため、中々な手間になる事があります. また std::map, std::set, std::queue, std::list, std::deque などはそもそも export 出来ないようなので注意が必要です.

https://support.microsoft.com/en-us/help/168958/how-to-export-an-instantiation-of-a-standard-template-library-stl-clas

(3) pimpl イディオムを利用する.

場合によってはアンチパターンと言われる事もありますが、pimpl をイディオムを使う事でもこの問題を回避出来ます.  デメリットはコードの記述量が増えてしまう点.

どうでしょう?  (1)(2) のに比べて危険なコードを書いてしまう可能性も低く、難しい事を考えなくても大丈夫かつ一番汎用性があるので、個人的には(3) を選ぶ事が多い気がしますが…

const でない空の std::string に対する先頭要素への添え字 (operator[])アクセスは C++11 以前は未定義

ちょっと作業をしていて、最近知った仕様.  Fix した規格書ではなく working draft からの参照になってしまいますが、多分内容は変わっていないはず.

C++ Working Draft – n1905
Returns: If pos < size(), returns *(begin() + pos ). Otherwise, if pos == size(), the const version returns charT(). Otherwise, the behavior is undefined.

C++ Working Draft – n4659
Returns: *(begin() + pos) if pos < size(). Otherwise, returns a reference to an object of type charT with value charT(), where modifying the object to any value other than charT() leads to undefined behavior.

「if pos == size() の時」と言う事で直接的にそう書いてある訳ではないですが、空の std::string の場合は size() == 0 なので結果的に str[0] は未定義になります.

古い処理系を使用していて、本当は未定義だけど気を効かせてくれて non const でも問題なしとなっている場合はあえて目くじら立てる事はないのかもしれないですが…

(2018年現在においても C++11 以前の別の処理系に乗り換える可能性があるようなケースでは別.  STL を使用しているケースは多くないと思うけど、Embedded 系は稀に良くある??)

にしても、勉強不足を痛感…

 

C++ で Read-Write lock パターン

同じデーターに対して複数のスレッドが入り乱れて読み書きを行う場合、排他のオーバーヘッドが無視できずに問題になることがあります.

そこで Read アクセスのみの場合は排他を行わなわず、Read-Write、Write-Write  がかち合った場合にのみ排他を行うことでオーバーヘッドの低減を図ろうというのがこのパターンです.

Read-Write lock パターンは shared mutex という形で標準ライブラリで提供されています (shared mutex の詳細については以下のリンクを参照下さい)

cppreference

C++ でマルチスレッドデザインパターン

C++ で Thread-Specific Storage パターン

複数のスレッドから読み書きされるデータはその内容の整合性を保つため、基本的には排他処理を行う必要があります(Single Thread Execution パターン

Thread-Specific Storage パターンは同じデータに複数スレッドからアクセスが行われ、かつスレッド間でのデータの共有が不要といったケースにおいて、スレッド毎に専用の領域を用意する事で排他を行わずに並列実行可能なようにすると言うパターンです.

C++11 から、新たな記憶クラス指定子として thread_local というものが用意されており、これを使うと簡単に Thread-Specific Storage パターンを使うことができます (thread_local 記憶クラス指定子についての詳細は以下のリンクを参照下さい)

cppreference

このパターンの実際の使い所ですが、積極的に使用するケースというのはあまりなく、「シングルスレッドでの使用を前提に静的領域を多用している(場合によってはレガシーな)コードを比較的簡単にマルチスレッド化する」といったような用途で使用される事が多いような気がします.

C++ でマルチスレッドデザインパターン

C++ で Thread-Per-Message パターン

Thread-Per-Mesasge パターンとは時間を要する処理要求に対し、一つ一つスレッドを割り当てる事で応答速度の向上を目的とするパターンです.

例えば以下は時間のかかるファイル保存の処理に Thread-Per-Message を適用した例です.

ここではダミーで標準出力へ出すようにしていますが、 Helper は処理の本体であるファイル保存を行うクラスで Host は client からの要求を受け、処理を行うスレッドを起動するクラスです.  スレッドはインスタンス化と同時に detach され、Host とは完全非同期に処理を実行します.(上記のような簡単な例だと Helper を利用するのが大げさに感じてしまいますが、本来のパターンに忠実に従い上では Helper を定義しています)

さてこの Thread-Per-Message パターンですが、実は使い所が大変難しいパターンだと思います.  なぜ難しいかと言うと スレッドは生成と同時に detach されて生成元のあずかり知らないところで動作を行うため、生成元で処理の終了やエラーの発生を感知できないからです.

では逆にどんな処理なら問題無いかと言うと

(1) 絶対エラーが発生しない (もしくは発生しても問題ないと) と分かっている単純な処理.
(2) deadlock 等が発生せず、一定の時間で終了することが保証されている処理.
(3) スレッドで実行される処理が非同期に動作するプログラムとして高い独立性を持っており、エラー処理等を完全に自己で完結出来る場合.

の場合のどれかに当てはまる場合でしょう.  (1) は言うまでも無いと思いますが、(2) の場合 deadlock を起こしたスレッドが開放されない事で徐々に OS のリソースを食いつぶして行き、最終的に処理速度の低下やスレッド生成の失敗等につながる恐れがあります.  (3) は Apache 等の Web サーバーがリクエストを捌く際に行う処理がまさにそれです (Apache の場合はリクエスト毎にスレッドでは無くプロセスを起動するので Process-Per-Message というのが正しいですが..)

というわけで今回取り上げたファイルに保存すると言う例も、ログファイルなどの「まぁ、問題が起こったら保存されなくてもしょうがないよね」というような用途以外で使用するのは適切ではありませんのでご注意を.

C++ でマルチスレッドデザインパターン