2018/02/25

[c/c++]仮引数を書かない場合と戻り型未指定を実際にやってみる

昔のC言語に仕様としてあり、今となってはほぼ使われていない書き方や、推奨されていない書き方がある。
いくつかあるのかもしれんが、私が気付くのはこの2つだ。

  • 戻り値の型を書かないとint
  • 仮引数を省略すると...


知識として持っていて、実際にやったことが無かったので、やってみた。

01: #include <stdio.h>
02: #include <stdarg.h>
03: 
04: func()
05: {
06:     va_list argptr;
07:     va_start(argptr, 3);
08: 
09:     const char *str = va_arg(argptr, const char*);
10:     printf("%s\n", str);
11:     int a = va_arg(argptr, int);
12:     int b = va_arg(argptr, int);
13:     return a + b;
14: }
15: 
16: int main(void)
17: {
18:     int res = func("hello", 3, 8);
19:     printf("result=%d\n", res);
20: }


書くのは書けたが、ビルドが通らない。。。

$ gcc -std=c89 -o tst noarg.c
In file included from noarg.c:2:0:
noarg.c: In function ‘func’:
noarg.c:7:5: error: ‘va_start’ used in function with fixed args
      va_start(argptr, 3);
      ^

funcを、

func(int num, ...)

として、呼び出しの第1引数に何か整数を入れておくと、warningが出る(second parameter of ‘va_start’ not last named argument)ものの通るし、実行できる。
va_start()の第2引数をnumにすれば、warningも消える。


https://linuxjm.osdn.jp/html/LDP_man-pages/man3/stdarg.3.html

va_start()の第2引数は、引数リストのうちで最後にならんでいる引数名、となっている。
うーん、では、「引数名を省略したら...扱い」かどうか確認できないではないか。。。


上記リンクの「注意」「バグ」に説明が載っていた。
stdargでは許されていない、ということで、<varargs.h>を使えば動かせそうだ。

#error "GCC no longer implements <varargs.h>."

あー、gccでは徹底的に使えなくなってるのですな。
残念だが、ここであきらめよう。


01: #include <stdio.h>
02: #include <stdarg.h>
03: 
04: func(int num, ...)
05: {
06:     va_list argptr;
07:     va_start(argptr, num);
08: 
09:     const char *str = va_arg(argptr, const char*);
10:     printf("%s\n", str);
11:     int a = va_arg(argptr, int);
12:     int b = va_arg(argptr, int);
13:     return a + b;
14: }
15: 
16: int main()
17: {
18:     int res = func(3, "hello", 3, 8);
19:     printf("result=%d\n", res);
20: }


こそっと、main関数はreturnなくてもOK、というやつも使っている。
自分でmainを呼び出す処理を書くときは、サブルーチンコールではなくてジャンプで飛ばしてスタックの消費を減らしたりしてしまいがちだ。
C11だと「noreturnマクロなんてものがあるから、今後はそれを書いた方が親切かもしれん。

2018/02/24

[tech]お悩みのKVS DB

愚痴というか、悩みというか、反省談というか。


いま作っているシステムでは、lmdbというKey-Value Storeのデータベースを使っている。
というといろいろ考えて選択したように見えるが、

  • SQL使うほどでも無い
  • C言語で使えるもの
  • よく使われている

というところで選んだだけだ。
そもそも、私はストレージが使えない組み込みシステムをやることが多かったので、DB自体くわしくない。


lmdbは、NoSQLと呼ばれる類のDBらしい。
https://ja.wikipedia.org/wiki/NoSQL

DBという全体集合があり、その部分集合としてSQL DBがある。
そして、NoSQLは、その反転した集合になりそうな気がするのだが、Wikipediaに書いてある"Not only SQL"が語源だとするならば、SQLを含んでいてもよいし、含まなくてもよい、ということになりそうだ。


lmdbは、SQL文は使わない。
DBの要素としては、こういうものがある。

  • environment
  • DB
  • key
  • data

まず、environmentという大きな広場があり、その中にDBを複数持つことができる。
そして、DBの中にkey-dataのセットでデータが格納されていく。

「トランザクション」というアクセス単位は、environment単位になる。
これに気付かなくてねぇ・・・。
トランザクションってDB単位でやるものだろうと思い込んでいたので、DBのオープンAPIにトランザクションが必要だとわかったときはショックだったわ。

イメージとしては、ドライブとディレクトリみたいなものか。
environmentがドライブで、DBがディレクトリ。
そして、ロックを掛けられるのはドライブ単位だ、と。

どうしても同時にロックしたくて、でもシステム上うまいことやれそうにないなら、environment自体を分けてしまうしかない。
システムを作って半年以上あれこれいじっているが、設計がよくなかったのか、lmdb以外だったらうまくやれたのか、未だによくわかっていない。


結局、environmentを分けることで対応したのだが、未だにどうするのがよかったのか、あるいはどうする方がよいのか、私の中で結論が出ていない。

SQLとかリレーショナルとか、そういう関連性がないということだけはわかっている。
そして、C言語から使いたい。
あとはメンテナンスされていっているとか、枯れているとか、そのくらいか。


https://ja.wikipedia.org/wiki/NoSQL
https://en.wikipedia.org/wiki/NoSQL


うーん、jaとenのWikipediaを並べてみたのだが、内容はともかく、数が多い!
これだけあるということは、特色がそれぞれあって、望む形はいろいろあるということなのか・・・。
「Aである」という事象はAだけだが、「Aではない」という事象は無限にあるのと同じか。

つまり、私が"lmdbでよかったんだろうか"という悩みは、ある意味では当然のことというわけだ。
無限にあるものの中から正解を1つだけ見つける、ということはあり得ないからだ。

[c/c++]memory.hは標準ヘッダではない

よくやってしまうのだが、malloc()やmemcpy()を使ったのに、ヘッダファイルを追加し忘れていることがある。
まあ、コンパイル時にエラーが出るので分かるのだが、問題はその次だ。
どれをインクルードするとよいのだっけ?


ふっと思いつくのが、連続データに対する処理だからストリング命令、ということで<string.h>。
しかし、心の中で「もしかしたら<memory.h>だったかも・・・」と心配になる。
コンパイルすればわかるのだけど、なんか追加した標準ヘッダを間違えるって、なんか素人っぽいじゃないか。
できれば、修正後は一発で正解したいのだ。

そして、malloc()/free()は<stdlib.h>だということを思い知らされるのである。。。

どうでもいいけど、strcpy()なんかも<string.h>に入っていると、文字列用のヘッダだと勘違いしてしまうよね。


それはさておき。
<stdlib.h>と<string.h>をインクルードすれば何とかなるのはわかるが、では<memory.h>には何が入っているのだろう?

オライリーのCクイックリファレンスで調べてみたのだが・・・<memory.h>は標準ヘッダではなかった!
そうか、そうなんだ・・・。

/usr/include/memory.hを見ると、<features.h>と<string.h>をインクルードしているだけだった。
<features.h>なんて通常はインクルードさせたことが無かったのだが、GLIBCのバージョンなどいろいろ定義されている。
じゃあ、単に<string.h>をインクルードするより<memory.h>の方がいいのか?と思ったが、<string.h>も先頭で<features.h>をインクルードしていた。


というわけで、私の結論はこうだ。

  • <string.h>でも<memory.h>でもいい
  • <memory.h>は標準ヘッダではないので、すべてのコンパイラが持っているかは定かでは無い
  • 標準ヘッダでないからといって、<memory.h>をインクルードしていても変ではない


標準じゃないからといって、使って問題があるわけではないのだ。
それに、個人的には「str***()」は<string.h>に、「mem***()」は<memory.h>に、となっていた方がわかりやすいと思う。

2018/02/17

[btc]addwitnessaddressのアドレスからdumpprivkeyはできない

bitcoindの話だ。

bitcoin-cli getnewaddressすると、P2PKHのアドレスが返ってくる。
HDウォレットのインデックスがインクリメントされて新しいアドレスが生成されたということか。

そのアドレスについてbitcoin-cli addwitnessaddressすると、P2WPKHのアドレスが返ってくる。
ただし、現在(2018/02/17)のところ、bitcoindではnativeのP2WPKHアドレスを扱うことができない。
だから、P2WPKH nested in BIP16 P2SHというアドレスがが返ってきている。

nested in BIP16 P2SH形式のよいところは、見た目上はP2SHアドレスになっているというところだ。
これなら、segwitに対応していないウォレットであっても送金することができる。
じゃあ、その形式だけでいいじゃないか、となりそうだが、segwitのnativeアドレスには利点がある。
その場合には、トランザクションのサイズを小さくすることができるのだ。

nativeなP2WPKHの場合、今までのscriptSigの部分がまったく無くなり、witnessのセクションに移動する。
segwitにするとブロックに入るトランザクションの数が増えるというが、たしかにトランザクションの数は増えるものの、トランザクション自体のサイズについては別の話だ。
nested in BIP16 P2SH形式の場合、scriptSigにもデータを置かないと既存ウォレットとの互換性が無くなってしまうので、トランザクションとしてのサイズが多少増えてしまうのだ。
ブロックに入れる際にwitnessの部分は別の場所に入るため、そこまで影響は無いのかもしれんが、大きくなることはなるのだ。
これがnative形式だとトランザクション自体が別物という扱いになるからかscriptSigのサイズは0になり、署名などはすべてwitnessに押し込められる。


私が不勉強なので、ブロックに取り込むところなどのしくみをよくわかっていないのだが、まあ、そこはbitcoindなんかがやってくれるからいいや、と割り切っている。
フルノードの庇護を受けている間は、けっこう楽ができるのだ。


ただ、というか、しょうがないか、というところだが、addwitnessaddressでつくったアドレスはdumpprivkeyで秘密鍵が取って来れないのだ。
P2SHという時点であきらめてしまう気がする。


次の0.16ではBECH32形式のアドレスが使えるようになるとか。
そうなると、直接nativeの形式でアドレスを生成できるようになるのかもしれん。

2nd Layerというか、BOLTではfundingにnativeなアドレスを使うことになっているので、今の時点では何とかしてP2WPKHから送金させる必要があるのだった。

まあ、そういうこともあるさっ。

2018/02/15

[c/c++]getopt_long()のlongindexが返るのは戻り値が0のときだけ?→ときだけだった

(2018/02/17更新)

ずっと先延ばしにしてきた、getopt_long()の勉強をすることにした。

組み込みだと、引数とか設定できないじゃないか。
値はNVに保存して、起動時に読み出す。

そういう生活をしていたので、Linuxでツールを作るときも、設定ファイルを読ませるようにしているのだが、これの評判が悪い。。
「ファイルでやる意味が分からん」とまでいわれてしまい、しぶしぶ引数を列挙するタイプで対応していた。

が、似たような機能を同じツールで実現したいことが多くなり、引数で見分ける必要性が出てきた。
しぶしぶgetopt()で対応していたのだが、さらに引数が増え、使える文字が減り、自分でも訳がわからなくなってきたのだ。


以上、自分の中で、どうしてもやらないといけないという動機づけを行う作業でした。


最初に見たのが、こちら。

getopt_long関数の利用 - コマンドラインオプションの処理 - 碧色工房

まずは動かして考えよう、とWindowsのWSLでやってみたのだが、なんか、ときどきSegmentation Faultが発生する。


よくわからんので、こちらのサンプルも動かしてみた。

Man page of GETOPT

こちらは動いたので見比べたのだが、Man pageの方はlongindexを初期化しているし、戻り値が0のときにしかlongindexを使っていない。
最初の方は未初期化で、getopt_long()に任せている。


しかし、説明文を読む限りでは、longindexをNULL以外で指定しておけば、正常な場合はインデックス値が戻ってきそうである。
うーん、WSLの不具合かなぁ。。。


2018/02/17

Azureで動かしているUbuntuで確認したが、やはり同じ動作になった。
WSLよ、疑って済まん。。。


よく読めば、こちらのサイトにもちゃんと書かれていた。
getopt_long関数の利用 - コマンドラインオプションの処理 - 碧色工房


manとしては、こう書かれている。

longindex は、NULL でなければ、 長いオプションのインデックスを longopts からの相対位置として保持している変数へのポインターとなる。

つまり、「長いオプションのインデックスを」だから、短いオプションのときには更新しない、ということか。
読み取るのが難しいですよ。。。


長いオプションかどうか見分けたい場合は、longindexを-1などに初期化してからgetopt_long()を呼び出して、更新されたかどうか確認すればよいのかな。
そういうシーンは少ない気がするので、私の場合はNULLにしてしまうだろう。


せっかくなので、オプションの使い方で気になるところも見ておこう。
「値付きのオプションに、イコールを付けてよいのかどうか」問題だ。
ソースファイルは、工房さんのサンプルをそのまま使っている。

$ ./tst --clear 100
c = 1, 100


$ ./tst --clear=100
c = 1, 100


$ ./tst -c=100
c = 1, =100


$ ./tst -c 100
c = 1, 100


./tst -c100
c = 1, 100

あー、こうなるんだ。

長いオプションの方はイコールを付けて値、短いオプションの方はスペースを入れるか入れないかで値、という使い方が一般的なようだ。
短いオプションでイコールを使ってもうまくいくやつがあるけど、あれはアプリ側で取り除いてるのかな。