cutlassfish について

Embedded をバックグラウンドに持つ似非組み込みエンジニア。 現在はとある Web 系企業のバックエンドエンジニアをやってます. 気づけばもうすぐ 34 に... アラフォー目前...

インピーダンスとは?

最近またギター熱が再燃してしまい色々と機材あさりをしているのですが、ネットでみかける(特にギターとかオーディオ関連の)インピーダンスに関する記述が未だに乱暴で適当なのばかりだなぁ…. と思ったので酔った勢いで細かい箇所は端折りつつ、無謀にも解説してみます. 「やはりお前らのMVCは間違っている」ならぬ「やはりお前らのインピーダンスは間違っている」というやつです.

中学高校の物理で習うように直流電源と抵抗しかない回路における電流と電圧の関係は簡単で四則演算ができれば計算できてしまいます. でも残念ながら(?)実際の回路には抵抗だけではなくインダクタンスやキャパシタンス(要はコイルとコンデンサ)もあるし、電源も交流があるので(電源が交流の場合、回路内の電流と電圧の位相や振幅などは周波数に依存して複雑に変化する)こいつらが絡んでくると一筋縄ではいきません.

しかし、まったくお手上げと言うわけではなく、インダクタンス、キャパシタンス、交流電源を含む電気回路でも微分方程式とよばれる方程式を解く事で電流と電圧がどのように変化するのか調べる事は(それなりに大変ですが)可能です.

うーんでも難しい… なんとか直流におけるオームの法則のように簡単にならないものだろうか… という事で当初は半ば強引に考えだされたのが実はインピーダンスなのです.

具体的にどういう事かと言うと、「時間的な変化が見えなくなってしまう」という代償を払う事で微分方程式で記述された電流と電圧の関係がオームの法則みたいに四則演算になってしまう数学的に別のある世界(と言ったら語弊があるけれど)がなんと存在するので、その世界に回路の振る舞いを表す微分方程式を転生させて「もとの世界で電圧だったもの」と「もとの世界で電流だったもの」の間の関係を「もとの世界の抵抗」と同じように考えようと言うのがインピーダンスなのです. 実は.

V(t) = I(t) R                          (1)
V(jω) = I(jω) Z(jω)               (2)

(1) は元の世界の電流と電圧と抵抗の関係.  (2) は別世界での「もとは電圧だったものV(jω)」と「もとは電流だったものI(jω)」の関係です.  インピーダンスと呼ばれるものは (2) 式の中で Z(jω) と表記されているもので、一般に実数ではなく「複素数を含む角速度が変数の関数」になります.  (これについては V(jω) と I(jω) も同様)  (1) の式同様見た目はシンプルですが、複素数を含んでいることで振幅だけでなく位相についても周波数に依存してどのように変化するかがキチンと関係として表現されます.

最初ヘビサイドという人が(おそらく思いつきで)微分や積分の記号をあるルールにのとって四則演算できるものに置き換えると、なんか良くわからないけど色々辻褄があうぞ!と発見したのが、別世界への扉を開くきっかけになったそうです(ヘビサイドの演算子法)

ただ、ヘビサイド本人は演算子法の正当性を数学的に説明するところまではできなかったようで、数学的に正しいという裏付けは後世の数学者によってなされたようです.

数学的な裏付けがなされた事で、演算子法はフーリエ変換やラプラス変換といった分野と結びつき、それらをベースにしたさらに高度な解析や回路設計の方法が提案され現代社会の各所で役に立っています.

◯まとめ
とりあえず以下の事を得意げにいっている人は本当に理解しているか一度疑ってかかりましょう 笑

(1) インピーダンスが高い・低い
上記のようにインピーダンス自体は角速度をパラメータにした複素数値を取る関数になります.  よって正確には「インピーダンスの絶対値が高い・低い(ただしその絶対値も扱っている信号の周波数に依存して変化するので一概には言えない)」と言うのが正しいです.

(2) この信号はハイインピーダンスなので弱い
インピーダンスは信号というものの性質ではなく回路の性質です.  また、弱いというのはノイズが乗りやすいという事だと思いますが、ノイズの乗りやすさは前段の出力回路と信号受側の入力回路が有機的に結合した閉回路において発生・誘起されるノイズがどれだけ入力側に信号としてインプットされるかに依存しているため、正確な表現ではありません.

(3) インピーダンス・マッチング重要
高周波のような信号の反射が顕著に問題になる場合や電力を最大限に次段の回路に受け渡したいようなケースではインピーダンス・マッチングを取ることは重要になってきますが、オーディオ信号の場合は高周波でもありませんし、(次段がスピーカーなどの場合を除き)重要なのは電力を受け渡す事でもないので、一般にはこれは当てはまりません.

ただ、ビンテージエフェクターなどは出力段にバッファが設けられておらず、そのために接続先の入力インピーダンスによって音色が極端に変化するケースがあるようですので、このような場合においては入力インピーダンスを調整して狙った音色に持って行くことに意味はあると思います.  しかし、これに関しては本来の回路工学的な意味でのインピーダンス・マッチングとは目的も違う全く別の話になりますのでこの音色調整のための入力インピーダンスの調整を指してインピーダンス・マッチングと言うのは誤解を招くといいますか微妙な感じがします.

 

 

 

広告

gdb で golang のソースコードデバッグが出来ないと思ったらアホな理由だった.

gdb で C++ のソースをデバッグするときに 「b main」からの「r」をあまりに打ちすぎているが故に go でも無意識に同じ事やってしまい「あれ? hit したのに list でディスアセンブルしかできない!!」と暫くハマってしまいました…

--- 省略 ---
For more information about this security protection see the
"Auto-loading safe path" section in the GDB manual.  E.g., run from the shell:
info "(gdb)Auto-loading safe path"
(gdb) b main
Breakpoint 1 at 0x456300: file /usr/lib/go-1.6/src/runtime/rt0_linux_amd64.s, line 67.
(gdb) r
Starting program: /home/*******/Workspace/go/src/local/misc/misc

Breakpoint 1, main () at /usr/lib/go-1.6/src/runtime/rt0_linux_amd64.s:67
67              MOVQ    $runtime·rt0_go(SB), AX
(gdb) list
62      GLOBL _rt0_amd64_linux_lib_argc<>(SB),NOPTR, $8
63      DATA _rt0_amd64_linux_lib_argv<>(SB)/8, $0
64      GLOBL _rt0_amd64_linux_lib_argv<>(SB),NOPTR, $8
65
66      TEXT main(SB),NOSPLIT,$-8
67              MOVQ    $runtime·rt0_go(SB), AX
68              JMP     AX
(gdb)

なんて事はありません.  go の場合、func main のシンボルは main.main  ([package name].[function name]) なので breakpoint を set するには b main.main としなければならないと言うだけでした… なんか、

warning: File "/usr/share/go-1.6/src/runtime/runtime-gdb.py" auto-loading has been declined by your `auto-load safe-path' set to "$debugdir:$datadir/auto-load".
To enable execution of this file add
add-auto-load-safe-path /usr/share/go-1.6/src/runtime/runtime-gdb.py

みたいな warning が出てたので言われるがままに  .gdbinit に追加してみたり、デバッグ情報が入っていないのか?と思って go build の引数いじってみたりしてしまいましたが、全然関係ありませんでした.

分かった後によくよく見てみれば、自分の書いたコードではなく lib の中身に set されているじゃないかという.

Breakpoint 1 at 0x456300: file /usr/lib/go-1.6/src/runtime/rt0_linux_amd64.s, line 67.

一発 nm &  grep main でもすれば直ぐに気づきそうなものの、いつもの流れで下手に break 張れて hit までしてしまったので、完全に思考停止してしまっていました.

つくづくアホな事で時間を使ってしまいました (反省)

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 系は稀に良くある??)

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

 

GNU Make を単体テストのテストランナーに使用しようとして困ったことあれこれ

make test で単体テストが実行されるようになっているプロジェクトを CI/CD に対応させる際に色々と右往左往してしまったので make マスターの面々からするとヤレヤレな内容かもしれませんが… メモがてら.

ビルドシステムに make が採用されている場合、多くの場合サブディレクトリ毎に makefile が用意されていて、それをプロジェクトのトップディレクトリに置かれた makefile から再帰 make するというのがよく行われると思います.  例えば以下のような構成のプロジェクトでは (a) から (b), (c) の makefile を再帰 make すると言った具合です.

├── Makefile          — (a)
├── subdir0
│      └── Makefile  — (b)
└── subdir1
│      └── Makefile  — (c)

また、(b) (c) の makefile には疑似 ターゲットとして test が定義されており、各々のディレクトリで make test を打つことで lib の生成や単体テストのビルドと実行が行われるようになっています.  このプロジェクトを CI/CD 対応しようとしたとき、ツール側から沢山あるサブディレクトリを一つ一つ個別に叩いて行くのは面倒なので当然 (a) に定義された test ターゲットを叩いて済ませたくなる訳ですが、単体テストの実行には一般的に以下の要求事項があり、この要求を満足する形でテストランナーを実装しなくてはなりません.

(1) 途中で fail しても後続のテストは実行したい.
(2) 全てのテストを実行し終えた後に一つでも fail していた場合に全体の終了ステータスをエラーにしたい.

さてどうしたものかと言うことで、真っ先に思いついたのが for でサブディレクトリを回す方法ですが、この方法では呼び出し元の make にエラーが帰らないため、(2) の条件を満たす事ができません.

次に思いついたのが、以下のように test ターゲットのコマンド行に再帰 make を直書きして -k (–keep-going)オプションで make を起動するという方法でしたが、-k オプションはターゲットのコマンド行で発生したエラーを無視して続行するのではなく、エラーを起こしたターゲットの構築を中断して、別のターゲットの構築を続けて実行するというオプションのようなので期待した動作をしません.  (というのをやってみて気づいた)

Normally make gives up immediately in this circumstance, returning a nonzero status. However, if the ‘-k’ or ‘–keep-going’ flag is specified, make continues to consider the other prerequisites of the pending targets, remaking them if necessary, before it gives up and returns nonzero status. For example, after an error in compiling one object file, ‘make -k’ will continue compiling other object files even though it already knows that linking them will be impossible.

https://www.gnu.org/software/make/manual/html_node/Errors.html

そうこうして最終的に「サブディレクトリ毎に test_subdir0, test_subdir1 のようなテスト専用ターゲットを (a) に用意して -k オプションを付けて叩くしかない」という結論に至ったのですが、実際はディレクトリの数も多く、ベタ書きはダサいなー嫌だなーと言うことで無い知恵絞ってヒネリだしたのが以下

なんかとりとめもなくなってしまった&もっといい方法がありそうな気がしてならなですが、とりあえず要件は満たせた!??