試験運用中なLinux備忘録・旧記事

はてなダイアリーで公開していた2007年5月-2015年3月の記事を保存しています。

Vala言語で文字列オブジェクトのメンバ関数scanf()を使用する上でのメモ

Vala言語の文字列オブジェクトのメンバ関数scanf()を用いると、ある文字列オブジェクトの書式を最初の引数の書式に基づいて解析し、その後ろにフォーマットに対応するように渡した引数の変数に値を設定する。これはC言語の標準ライブラリに含まれるsscanf()に相当し、実際に中間ファイルのC言語のコードでもこの関数が用いられるのだが、代入先が文字列オブジェクトの場合に注意が必要となる。

長さを指定して文字列オブジェクトを確保しておく

C言語における標準Cライブラリのsscanf()では書き込み先となるchar配列を十分な長さで確保することになるが、Vala言語の場合は「十分な長さを事前に確保したstringオブジェクト」が必要となる。
これを実現する(書き込み先)文字列オブジェクトの生成方法がstring.nfill()で、1番目の引数で指定したサイズだけ2番目の引数のコード*1を持つ文字が書き込まれた文字列オブジェクトが得られる。例えば

string aaa = string.nfill (15, 'a');

とすると、15文字の「a」で埋められた状態で初期化された*2文字列オブジェクトaaaが得られる。
ただ、この方法が最もふさわしい方法なのかは分からず、リファレンスを見ても引数が可変長であることしか分からない。

テスト

下のコードでは、string.nfill()を用いることが分かる前に色々試して失敗したものも含めてテストを行っている。
書き込み先としたい文字列オブジェクト「name」を

  • 値が未定義
  • nullで初期化
  • 空文字列で初期化
  • string.nfill()でサイズ指定*3して初期化

上のそれぞれの方法で用意し、解析対象の文字列(文字列と数値を含んでそれぞれ設定するようにする)も別途用意しておいてメンバ関数scanf()で解析して値を設定し、その後それらの値を表示するというものとなる。

[任意]ファイル名: scanftest.vala

/*
 * 値が未定義(コンパイルエラー):
 *  valac -o scanftest_unassigned scanftest.vala
 * 空文字列で初期化:
 *  valac -D EMPTY -o scanftest_empty scanftest.vala
 * nullで初期化:
 *  valac -D NULL -o scanftest_null scanftest.vala
 * 正常動作:
 *  valac -D NFILL -o scanftest_nfill scanftest.vala
 */

namespace ScanfTest
{
  class MainClass
  {
    public static void main (string[] args)
    {
      /*
       * 解析する対象の文字列(テストなので内容に深い意味はない)
       * 「[文字列]=[整数]」の書式を満たすものとする
       */
      string str = "abcdefghijk=15161718";
#if NFILL
      /* 63文字のNULL文字+終端のNULL文字で文字列を初期化 */
      string name = string.nfill (63, '\0');
#else
# if NULL
      /* null初期化すると正しく動かない */
      string name = null;
# else
#  if EMPTY
      /* 空文字列で初期化するとabortする */
      string name = "";
#  else
      /*
       * 値が未定義だとコンパイルエラーになる
       * "use of possibly unassigned local variable"
       */
      string name;
#  endif
# endif
#endif
      int val;
      /*
       * strの内容を解析し、nameとvalに格納
       * 値の格納先は未定義OKな参照(out)かアドレス(&)で渡す
       * stringオブジェクトは「out」や「&」を付けないで指定
       */
      int retval = str.scanf ("%[^=]=%d", name, out val);
      /* 「&」を用いる場合はvalを適当な値で初期化しておく必要がある */
      //int retval = str.scanf ("%[^=]=%d", name, &val);
      print ("str.scanf():%d\n", retval);
      /* 2つの変数に正常に書き込めたら表示 */
      if (retval == 2)
        print ("name:%s val:%d\n", name, val);
    }
  }
}

結果としては、nameについて

  • 「値が未定義」の場合:コンパイルエラーになる
  • 「nullで初期化」の場合:挙動が変
  • 「空文字列で初期化」の場合:異常終了(abort)する
  • string.nfill()でサイズ指定して初期化」の場合:正しく動作

という動作となった。
以下は正しく動作するときの出力となる。

$ ./scanftest_nfill
str.scanf():2
name:abcdefghijk val:15161718

きちんとそれぞれにデータが入っていることが確認できる。

関連記事:

参考URL:

使用したバージョン:

  • Vala 0.7.9
  • GLib 2.22.2

*1:C言語と同様、文字をソース中に表記する場合はシングルクォートで囲む

*2:最後には終端のNULL文字が付く

*3:長めに63文字とした