2018年4月24日

[c]初期化用マクロを用意するとき

protobuf-cを見ているときだったが、構造体の初期化をするのに、関数とマクロの2つが用意されていた。
関数はアドレスを渡せばどこでも初期化できるのだが、マクロは構造体のインスタンス宣言に代入する形でないと使えない。


言葉で書くとわかりづらいので、コードにしておこう。

01: typedef struct {
02:   int a;
03:   const char *b;
04: } aaa_t;
05: 
06: #define INIT_AAA    { 0, NULL }
07: 
08: void init_aaa(aaa_t *val)
09: {
10:    val->a = 0;
11:    val->b = NULL;
12: }


マクロの場合は、構造体の初期化に限定した書き方になるので、宣言時の代入でしか使用できない。
C++であればコンストラクタで済ませられるのだが、Cではどうしようもない。


「じゃあ、初期化関数だけでいいやん」と私は思っていたのだ。
明示的に初期化したのが分かるし、そんなに面倒な書式ではない。


そう思って、自作のライブラリでもそうしていたのだが・・・だんだん嫌になってきた。
例えば、上記のコードを使うと、こうなる。


aaa_t     a;
init_aaa(&a);


大したことは無い。
では、こうなってきたらどうだ。


int k = 0;
aaa_t    a1;
aaa_t    a2;
bbb_t    b;
ccc_t    c;
char *str = NULL;

init_aaa(&a1);
init_aaa(&a2);
init_bbb(&b);
init_ccc(&c);


こういう混在が続いてくると、初期化するものが続いているときには、その場で初期化するようにした方が間違いが少ないんじゃなかろうか、という考えが芽生えてきたのだ。


おじさんでも、芽生えるときは芽生えるのだ。
ただ、それが良い方向かどうかは分からんがね。
「コードリーティング」を読んだ方が良いのかもなぁ。

2018年4月22日

[tech]組み込み向けにするのをあきらめるとき

お仕事でプログラムを作っている。

一人で作っているのだが、設計も何もせずにやっている。
よろしくはないのだが、そもそもどういう動きをするようになるのかわからずに作るという、試作の手前のようなものである。


で、最近見直していると、同じようなことをやるのになぜか3つの実装方法をわざわざ使っていることに気付いた。
「同じようなこと」は、データを次々にためておく方法だ。

  • キュー
  • リングバッファ
  • リスト

我ながら「はぁ?」となってしまった・・・。
まあ、キューもリストも似たようなものではあるのだが、「キュー」は自分で実装したのだが、「リスト」はLinuxのリストであるLIST_HEADやLIST_ENTRYを使っている。
作ったのが別の人ならまだしも、私しかいないのに、なぜ。。。


プライオリティがなければ、データの詰め込み方はFIFOかFILOか、キューかスタックか、くらいしかないと思う。
リングバッファは、FIFO型になるのか。


たぶん、私の思考過程からすると、最初に作った「キュー」は、そこまで数が増えないけど、数の予測が付かなかったので、自作したのだろう。
その次がリングバッファになったのは「増えすぎたらキャンセルしてしまえ」と思ったからではなかろうか。

とにかく、メモリサイズがはっきりしない状況が嫌だったのだ。
何とかして、サイズを支配したかったようだ。


最後に作ったのはLinuxのリストを使ったもので、この頃になると自作するのも嫌になったし、サイズを考えるのにも疲れてきたのだろう。
そう、設計どうこうではなく、疲れたのだ。
私らしいといえば、私らしい。


そして、もう1つ。
作っているうちに、こりゃLinuxが動くレベルのマシンじゃないと実装できない、ということに気付いたことだ。
Cortex-Mなどの組み込みソフトにしてしまうことをあきらめたのだ。
これはこれで「疲れた」と表現できるかもしれない。


Raspberry-Pi1くらいあれば動くが、そこは私にとってはもう大きめの組み込みになる。
ストレージを付けたり外付けメモリとかやればCortex-Mでもできなくもないのかもしれないが、そこまでして実現する意味はあまりないだろう。
ハードウェアを安めに仕立ててソフトウェアががんばる、という時代でも無いと思う。
そういうことずっとやってるから、いつまで経っても仕事が減らんのじゃー。


と、それっぽい結論で終わらせようとしたが、実装が3種類もあるのはよろしくないので、どれかにまとめよう。

2018年4月14日

[vscode]WSLの配色が直る

こちらの過去記事をご存じだろうか。


[vscode]WSLのターミナルが赤くなった
http://hiro99ma.blogspot.com/2017/12/vscodewsl_4.html


2017年の12月に書いた記事なのだが、これがさっきまで全然解決しなかったのだ。
ColorToolというツールを動かすことで、ようやく直った。


しかし、どういうことだったのだろう?
ColorToolはコンソールの配色を変更するツールのようなのに、vscodeにまで影響があったのか。
あるいは、コンソールに関する色設定がおかしくなっていたのだろうか。
他の環境で発生していないことからすると、後者の方があり得そうだ。

でもなぁ、cmdとかPowerShellなんかは普通だったし、Ubuntuのコンソールもまともだったのだよ。


いろいろ腑に落ちないのだが、新しい解決方法が身についたのは喜べることだ。

[win10]最近あったトラブルたち

昨日のことだった。

会社で使っているWin10 Homeのマシンの調子がよくなかった。
アクションセンター(タスクトレイの右端にある、吹き出しっぽいアイコン)をクリックしても、何も出てこない。
タスクトレイに置いているExplorerのアイコンを右クリックしても、何も出てこない。
スタートメニューはClassic Shellを使っていたから気付かなかったのだが、通常では左クリックしても出てこない。

むう・・・。


イベントビューアを見ると、Cortanaがどうとか。
確か、何かのツールを使ったときに無効化されていたと思うが・・・と見てみると、アンインストールされていた!
通常はアンインストールしようとできないので油断していたのだが、何かをトリガにして消えてしまったようだ。


ネットで検索すると、PowerShellを使ってあれこれやる方法が見つかるのだが、どれをやってもダメ。
昔なら、ストアアプリ系が動かなくてもなんとかなったのだが、いまや設定アプリがストアアプリになっているものが多く、作業できなくもないのだが、非常に不便になることが目に見えていた。


最終的に効果があったのは、Media Creation Toolを使っての再インストール。
私の知っている「再インストール」は、とりあえずWindows周りを再設定し直す、というものだった。
しかし、ネットで検索していると、どうも最近の「再インストール」は、データは削除しないしストアアプリは何とかするけど、それ以外のものは使えなくなる可能性は高いかも、というものが出てきていた。
私が思っていた「クリーンインストール」が、今の「再インストール」になりつつある気配を感じている。


時代が変わったということか。。。
あるいは、レジストリモデルはうまくいかない時代になったと言うことかもしれない(うまくいっていたのかどうかは知らん)。


Media Creation Toolで再インストールするという決断に至るまで、けっこう試したと思う。
wsrreset.exeやトラブルシューティングツールもエラーになるくらいだったから、どうしようもなかったのだろう。
前日までは問題なかったから、Windows Updateをトリガにして何か起こったのか、蓄積していた不具合が一挙に出てきたのか、あるいは・・・。

いや、意味の無い考察はやめよう。



再インストールをMedia Creation Toolでやった結果を残しておこう。
2018/04/13時点のWindows10 Home(Windows Updateは最新)での結果だ。

メディアを作らずに直接再インストールしたが、インストール済みのWindowsアプリ(ストアアプリじゃないやつ)は生き残っていた。
ストアアプリは、起動するようになった。
アクションセンターは、表示されるようになった。
使わないストアアプリはアンインストールしていたのだが、復活していた。Cortanaも戻った。
Shell Newは元に戻った。
Quick Launchにデスクトップ表示などのショートカットが復活した。
ATOKの認証が戻されて、ライセンス入力が必要だった。

全部は試していないのだが、困らないところまでは復帰してくれた。


こういう現象とは別に、以前から起きているものがある。
今回の再インストールをしても直らなかったものだ。


何かというと、Office系のアプリで、画像の挿入を行おうとしてエクスプローラのウィンドウが出たとき、サムネイルがほぼ表示されない、という現象だ。

「ほぼ」が何かというと、一度WindowsのExplorerでサムネイルが表示されているものは、Officeでも表示されているらしい。
Officeで使用されるExplorerにサムネイル作成機能が無い、とも言えるのか。


Office系だけなのかどうか調べたかったのだが、他に画像の挿入を思想なアプリが分からなかった。。。
WireSharkとかWinMergeじゃダメだろうしねぇ。


検索したけど、

  • Explorerの設定がアイコン表示になっている
  • Officeデータをサムネイル表示にしたい

がたくさん出てきて、見つけられなかった。。。


もう1つ。

Edgeを使っている人がいたのだが、「コピー&ペーストをするとウィンドウが閉じる」という話を聞いて、そんなわけなかろう、と操作してもらうと、本当にそうだった・・・。

ウィンドウが閉じるというか、強制終了なのだと思う。
毎回起きるわけではないけど、起きるときには何度やってもダメっぽい。



こういう現象を見ると、ネットで「××がうまくいきません」みたいなQと「○○すればできます。ネットで検索したらすぐ出てきました」というAがよくあり、私も読んで「検索してないのかな?」と思うことがあるのだが、実際目にすると、なかなかそうも行かないパターンもあるので、気をつけねばならんですな。

2018年4月1日

[c/c++]protobuf-c (7)

protobuf-cの使い方を調べている。

前回、ProtobufCBufferSimpleが何か使えるんじゃなかろうかと思ったのだが、packに使うくらいしかなさそうだ。

https://gist.github.com/hirokuma/c79f5c80cfee9db8cc3c968ac4f46819

置き換えてみたのだが、packするサイズは事前に調べられるので、この使い方だと意味は無いな。

repeatedメンバーで個数が増えるときにappendするのがよさそうだが、realloc()で参照するメンバのアドレスを追加し、malloc()でメンバを追加していく方が素直に見える。


protobuf-cを使っているオープンソースが見つけられていなくて、うまい使い方が分からないのだ。
自分で見つけるしかないのか。


サンプルアプリのメモリ解放漏れが気になるので、valgrindで確かめよう。
・・・WSLだと、freeをわざと外しても"All heap blocks were freed -- no leaks are possible"となって信用できんので、ノーマルなUbuntu環境に立て直そう。


git clone https://github.com/google/protobuf.git
cd protobuf
./autogen
make
sudo make install

cd ..
git clone https://github.com/protobuf-c/protobuf-c.git
cd protobuf-c
./configure

protobufのヘッダがないというエラーになった。。。
しかし、config.logを見ると、-std=c++11 or -std=gnu++11と出ていた。

configure:17261: checking google/protobuf/compiler/command_line_interface.h usability
configure:17261: g++ -c -g -O2  -pthread -I/usr/local/include conftest.cpp >&5
In file included from /usr/include/c++/5/mutex:35:0,
                  from /usr/local/include/google/protobuf/stubs/mutex.h:33,
                  from /usr/local/include/google/protobuf/stubs/common.h:52,
                  from /usr/local/include/google/protobuf/compiler/command_line_interface.h:41,
                  from conftest.cpp:61:
/usr/include/c++/5/bits/c++0x_warning.h:32:2: error: #error This file requires compiler and library support for the ISO C++ 2011 standard. This support must be enabled with the -std=c++11 or -std=gnu++11 compiler options.

では、足してみよう。

./configure CPPFLAGS=-std=c++11
make
...
error: ‘scoped_array’ does not name a type

configureは通ったが、makeでエラーだ。
しかし、WSLでprotobufをビルドしたときにはエラーが出なかったはずだ。

configure:17261: checking google/protobuf/compiler/command_line_interface.h usability
configure:17261: g++ -c -g -O2 -I/home/xxx/prog/clang/protobuf/protobuf-c/../../../cpplang/protobuf/install/include -pthread conftest.cpp >&5
configure:17261: $? = 0
configure:17261: result: yes


何か見落としているのだろうか。。。

2018年3月25日

[c/c++]protobuf-c (6)

前回作った、protobuf-cの書込みプログラムを見ていこう。

Githubのgistにも残しておこう。
結果の出力をファイルに変更している。
https://gist.github.com/hirokuma/11fed489bd97fa62daed686dec622646


ルールとして、xxx__INITを使った初期化は、インスタンスの定義と同時に行うしかない、というものがある。
こんなところや、こんなところである。

addrbook.peopleなんかは、malloc()してmemcpy()なんかでコピーさせたかったのだが、初期値用のインスタンスを作っておいて、それをmemcpy()(実装では代入)させている。
まあ、forの中で初期化させる意味は無いので、外に出した方がよかろう。


よく見ると、tutorial__address_book__init()という関数も用意されていた。
やってることは、同じだ。

01: void   tutorial__address_book__init
02:                      (Tutorial__AddressBook         *message)
03: {
04:   static const Tutorial__AddressBook init_value = TUTORIAL__ADDRESS_BOOK__INIT;
05:   *message = init_value;
06: }

static constになっているから、メモリが静的に割り当てられそうな気がするけど、ROM側になるからそこまで気にしなくてよいか。
マクロでの代入と関数での初期化をそれぞれやると、それぞれメモリを取りそうだから、複数使うのであれば関数呼び出しにした方が小さくできるかもしれない。

いっそのこと、static inlineにしてヘッダに置いた方が・・・などと思ったが、初期化にそこまで気合い入れなくてもいいんじゃなかろうかね。


それ以外は、取り立てて書くことがないなー、と思っていたのだが、ヘッダファイルを見ると使っていない構造体があった。

  • ProtobufCAllocator
  • ProtobufCBuffer

pack_to_buffer()とunpack/free_unpacked()の引数で使われている。
名前からすると、メモリ関係をやってくれそうだが、どうだろう?


https://github.com/protobuf-c/protobuf-c/wiki/The-protobuf-c-Library#virtual-buffers

ProtobufCBufferは、append()を持つようだ。
ここでは、FILE*への書込みを例にしている。

gistに置いたサンプルでは、get_packed_size()でサイズを取ってきて、malloc()してpack()、とやっているが、ファイルに吐き出していくのであれば、サイズは気にせず、malloc()もいらず、pack()の代わりにpack_to_buffer()でオープンしているFILE*に吐き出していけばよいだろう。


RAMに置きたい場合は、ProtobufCBufferSimpleが使えるかもしれん。
ただ、これは配列でメモリを確保したものを与えるらしい。
しかし、allocatorとしてmalloc/free()を使ってもいるようだ・・・。
なんだこりゃ?

protobuf_c_buffer_simple_append()を見た感じでは、初期バッファを超えない間は配列を使い、超したらmallocしていくのかな?
realloc()ではなくmalloc()しか使って無さそうだけど、リークしないんだろうか?
メモリを確保して、コピーしてからfreeしているような感じもするので、大丈夫なのか。


全部見ていく気力は無いので、動かしてみるのがよいかな。

2018年3月22日

[c/c++]NULLチェックの悩み

私は、引数のNULLチェックをあまりやらない方だ。

NULLにする場合がある(NULLだったらこう動く)という場合には調べるが、与えられたポインタがNULLだったらエラー、みたいな処理は、あまり書きたくない。


そうは思いつつも、やはり、悩む。
たまに、NULLチェックしさえすればすぐに不具合の場所がわかったのに、ということが起きるからだ。
OSがある場合には、比較的見つかりやすいと思うのだが、OSが無い場合だと、なかなか見つけにくい。
NULLはだいたい(void*)0みたいな定義になってると思うが、アドレス0はベクタテーブルが配置されていることが多く、単に書き込めなないアドレスに書き込んだというだけでスルーされてしまうからだ。


私がNULLチェックしない理由は簡単で、それはコーディングのバグであることがほとんどだからだ。
コーディングのバグをチェックして、エラーを返したとしよう。
それで、エラーの処理を正しく行って、アプリが生き続けたとして、それに何の意味があるのだろうか。
いっそのこと、アプリが異常な動作をしてくれた方が助かるではないか。


だから、チェックしたい場合は、assert()を使うことが多い。
それだと、NDEBUGしてなければ即座にabortするし、NDEBUGしたらコードとして残らない。
そういう経路は、デバッグで潰してしまえ、というわけだ。


もしチェックできないままだった場合、NDEBUGするとスルーして動くだろう。
それ以降の動作は、どうなるか想像できない。
もしエラーチェックしていれば、少なくとも想定した動作にはなるだろう。
だったら・・・エラーチェックした方が、まだ動作の推測ができる分、ましなのか???



そんなことを悩みながら、日々生きております。

2018年3月18日

[c/c++]protobuf-c (5)

ようやく、本題のprotobuf-cだ。

https://github.com/protobuf-c/protobuf-c

protocへPATHを通しておく。

git clone https://github.com/protobuf-c/protobuf-c.git
cd protobuf-c
./configure

...

configure: error: required protobuf header file not found

怒られた。。。って、これは1回目と同じ話じゃないか。
C++のprotobufはローカルのディレクトリにインストールしたので、確かに探せないのは分かる。

こんな感じにすると、configureできた。
CFLAGSではダメで、CPPFLAGSを使うというのがポイントか。
また、インストール先はC++版と同じ場所にしておいた方がよさそうだ。

./configure --prefix=`pwd`/../../../cpplang/protobuf/install CPPFLAGS=-I`pwd`/../../../cpplang/protobuf/install/include LDFLAGS=-L`pwd`/../../../cpplang/protobuf/install/lib
make
make install


protocへPATHを通しているが、それはcpplang/protobuf/install/binに方になっている前提で話を進める。
protobuf-cでmake installしたときにバイナリがそこへコピーされるためだ。


まず、--c_outを試す。

mkdir c
protoc --c_out=./c/ addressbook.proto

pb-c.cとpb-c.hが生成されたが、もちろんC++版とは違う内容だ。

ヘッダの構造は、こういう感じだ。

image

アンダースコア2つでつないである。
先頭がアンダースコアなものは、structのタグ名だ。
特に使っていないようだから、最初からtypedefだけでもいい気はするが、まあよかろう。


ソースファイルは、こう。

image

見た目でstaticかどうか区別が付かないのだが、Functionは全部public、Variableは「__descriptor」とついているものがpublic constだ。


では、googleのサンプルと同じようなものを作っていこう。

https://github.com/protobuf-c/protobuf-c/wiki/Examples


01: #include <stdio.h>
02: #include <stdlib.h>
03: #include <string.h>
04: 
05: #include "addressbook.pb-c.h"
06: 
07: 
08: int main(int argc, char *argv[])
09: {
10:     Tutorial__AddressBook addrbook = TUTORIAL__ADDRESS_BOOK__INIT;
11:     uint8_t *buf;
12:     unsigned int len;
13: 
14:     addrbook.n_people = 1;
15:     addrbook.people = (Tutorial__Person **)malloc(sizeof(Tutorial__Person*) * addrbook.n_people);
16:     for (int lp = 0; lp < addrbook.n_people; lp++) {
17:         addrbook.people[lp] = (Tutorial__Person *)malloc(sizeof(Tutorial__Person));
18:         Tutorial__Person init = TUTORIAL__PERSON__INIT;
19:         *addrbook.people[lp] = init;
20:     }
21: 
22:     addrbook.people[0]->id = 1;
23:     addrbook.people[0]->name = "yoshio";
24:     addrbook.people[0]->email = "yoshio@mail.com";
25: 
26:     len = tutorial__address_book__get_packed_size(&addrbook);
27:     buf = (uint8_t *)malloc(len);
28:     tutorial__address_book__pack(&addrbook, buf);
29:     fprintf(stderr, "Writing %u serialized bytes\n", len);
30:     for (size_t lp = 0; lp < len; lp++) {
31:         printf("%02x ", buf[lp]);
32:     }
33:     printf("\n");
34: 
35:     for (int lp = 0; lp < addrbook.n_people; lp++) {
36:         free(addrbook.people[lp]);
37:     }
38:     free(buf);
39:     return 0;
40: }

$ gcc -o tst -I../install/include addressbook.pb-c.c write.c -L../install/lib -lprotobuf-c
$ LD_LIBRARY_PATH=../install/lib ./tst
Writing 29 serialized bytes
0a 1b 0a 06 79 6f 73 68 69 6f 10 01 1a 0f 79 6f 73 68 69 6f 40 6d 61 69 6c 2e 63 6f 6d

C++版のファイルと比較しよう。

image

おお、一致した。


やっぱりというかなんというか、動的なメモリ管理が面倒だ。
repeatedもそうだし、char*もだ。
phonesを入力しようとしたら、またそっちも同じようにmalloc/freeがいるので、さらに面倒になるのだ。


もう少しやってみるが、protobuf-cは後から導入するよりも、最初から使うつもりで考えていないと、大変そうだ。
例えば、最初はchar[20]みたいな配列で管理していたものが、後から置き換えてchar*による動的管理になると、解放漏れなんかやってしまいそうだ。

あるいは、シリアライズしたいときだけコピーする、という方法があるか。
shallow copyしてアドレスだけ渡してやれば済むし。
ただ、repeatedのrepeatedなんかが出てくると、やはりめんどうか。


実はメモリダンプで十分、というときもあるだろうが、まあ、まずは使い慣れないとね。
使える道具が少ないと、「金槌を持っている人はすべてが釘に見える」みたいなことになりかねん。

[c/c++]protobuf-c (4)

タイトルと違って、C++のprotobufを見ているが、今回までそれをやって、次からprotobuf-cを見に行く予定だ。


では、前回の続きで、protobufのサンプルソースを見る。


まず、書込みから。

https://developers.google.com/protocol-buffers/docs/cpptutorial#writing-a-message

includeやコメントを入れても100行程度だ。


main()は、いきなり「GOOGLE_PROTOBUF_VERIFY_VERSION」というマクロで始まる。
VerifyVersion()を呼ぶマクロで、ライブラリが持つバージョンとヘッダファイルば持つバージョンをチェックして、許容できないバージョンであればabortさせているようだ。

まあ、if文が2つ入っているだけだし、コストはそんなに高くないだろうけど、自動生成したソースでは呼び出すようになっているらしい。
ルールとして最初に呼び出すだけにしておく方がよいように思うのだけど、何か理由があるのかいな?
protocにオプションがあるかと思ったが、各言語への変換はプラグインのようだから、そっちを見ないといかんのか。
思い出したら、見よう。


本体の構造は、シンプルだ。
tutorial::AddressBookのインスタンスを作り、引数で与えたファイル名があればParseFromIstream()で読み込む。
add_people()でAddressBook::Personのインスタンスを作り、それに中身を詰める。
SerializeToOstream()で出力し、メモリ解放APIを呼び出す。

チュートリアルにはシリアライズとして、std::stringとstd::fstreamが載っている。
他にもあるのかとソースを検索しようとしたが、どこがどうつながっているのかわからず断念。。。
C++のリファレンスページを見ると、ParsingとSerializerあたりのようだ。
Code / ZeroCopyStream / String / Arrayなのかな?


Peopleに中身を詰めるところは、そんなに凝ったことはしていない。
setterを使って引数に値を指定するか、mutableで直接書き換えるか。


では、読み込みも見よう。
書込みのサンプルに既存ファイルから読み込む箇所があったので、そんなに見なくてもよいかも。

https://developers.google.com/protocol-buffers/docs/cpptutorial#reading-a-message

こちらは70行に満たない。


main()で、引数で与えられたファイル名をParseFromIstream()で読み込んで、あとはリスト出力する関数を呼んで、メモリ解放。
リスト出力するサイズは、peopleがPersonのrepeatedだから、sizeで取得できる。
iteratorみたいなやり方では無くて、引数にindexを指定して取得している。


取り立てて、説明することも無かろう。


あとは、注意事項などが書かれている。

  • tagの数字は書き換えないこと(MUST NOT)
  • required fieldを追加/削除しないこと(MUST NOT)
  • optional fieldとrepeated fieldは削除できる(MAY)
  • optional fieldとrepeated fieldは新規追加できる(MAY)
    • が、tagは新しい数字を割り当てること(MUST)

requredは、削除だけじゃ無くて追加もできないのか。
過去バージョンとの整合性がとれなくなるからかな。


あとは、メッセージのオブジェクトはできるだけ使い回そうとか、tcmallocを使うのも検討しよう・・・って、tcmallocのリンク先がないやん。
これかな?


では、次回からようやくprotobuf-cを見ていくことにしよう。
C++版もいろいろ機能は残っているのだが、全部見ることができそうな気がしないし、シリアライズしてセーブ/ロードができるのだから、もういいだろう。


しかし、これとKey-Value Store型のDBは、まあまあ近い関係にあると思った。
私はlmdbしか使ったことが無いのだけど、あれはデータ構造が平たくなっていると思えばよいか。
データを残すだけだったらlmdbの方が簡単かもしれんが、メモリ上で使っている状態をそのまま保存しておきたいならprotobufの方がプログラムでのデータ構造とセットになっている分、後腐れが無い。
かといって、マルチプロセスでうまいこと運用したいとなるとDBの方がいいだろうし、そう簡単に割り切れるものではないか。

protobufではstd::stringやarrayでも吐き出せるので、KVSのデータの1つとしてprotobufを使うというのはありかもしれんな。

2018年3月17日

[c/c++]sscanf()でSSHっぽい名前を読む

C言語の標準ライブラリで文字列をパースしようとしたら、sscanf()が候補に出てくると思う。

数字のCSV形式を読むのに使っていて動いていたのだが、文字列をやろうとしてつまづいたので、記録を残しておこう。


01: #include <stdio.h>
02: #include <stdlib.h>
03: 
04: int main(void)
05: {
06:     const char STR[] = "yoshio@192.168.0.1:1234";
07:     char name[20] = "";
08:     char ipaddr[15 + 1] = "";
09:     int port = -1;
10:     int ret = sscanf(STR, "%s@%s:%d", name, ipaddr, &port);
11:     printf("ret=%d\n", ret);
12:     printf("name=%s\n", name);
13:     printf("ipaddr=%s\n", ipaddr);
14:     printf("port=%d\n", port);
15: }

ret=1
name=yoshio@192.168.0.1:1234
ipaddr=
port=-1


こうすると、期待通りになった。

01: #include <stdio.h>
02: #include <stdlib.h>
03: 
04: int main(void)
05: {
06:     const char STR[] = "yoshio@192.168.0.1:1234";
07:     char name[20] = "";
08:     char ipaddr[15 + 1] = "";
09:     int port = -1;
10:     int ret = sscanf(STR, "%[^@]@%[^:]:%d", name, ipaddr, &port);
11:     printf("ret=%d\n", ret);
12:     printf("name=%s\n", name);
13:     printf("ipaddr=%s\n", ipaddr);
14:     printf("port=%d\n", port);
15: }

ret=3
name=yoshio
ipaddr=192.168.0.1
port=1234

「文字列の中にデリミタの文字は入ってませんよー」という宣言がいる、ということかな。