ICU charset detector

ICU(International Components for Unicode)というライブラリのなかにcharset detectorという文字コード/文字種判定器がはいってます。

使い方は下のような感じ

#include <iostream>
#include <fstream>
#include <unicode/ucsdet.h>
#include <string>

int main(int argc, char** argv)
{
	if (argc > 2)
		return -1;

	std::string filename(argv[argc-1]);
	std::ifstream ifs(filename.c_str(), std::ios_base::in);

	if (!ifs.is_open())
		return -1;

	UErrorCode error = U_ZERO_ERROR;
	UCharsetDetector* detector = ucsdet_open(&error);
	if (U_FAILURE(error))
		return -1;

	std::string buffer;
	buffer.resize(65536);
	ifs.read(&*buffer.begin(), buffer.length());
	buffer.resize(ifs.gcount());

	ucsdet_setText(detector, buffer.c_str(), buffer.length(), &error);
	if (U_FAILURE(error))
		return -1;

	int32_t founded = 0;
	const UCharsetMatch** matchers =
		ucsdet_detectAll(detector, &founded, &error);
	if (U_FAILURE(error))
		return -1;

	if (founded > 0)
		std::cout << "filename: " << filename
				  << " name: " << ucsdet_getName(matchers[0], &error)
				  << " language:" << ucsdet_getLanguage(matchers[0], &error)
				  << std::endl;

	ucsdet_close(detector);
}

まぁこんな感じでさして使うのも難しくないライブラリなんですが、クラスでラッピングしていったら何を判定してもShift JISとしか判定しない@Windows Visual C++ 2008 Express と言う現象が・・・

最初はwindows版のbuild済みライブラリを使ってたのでそっちが変なのか?と思ってソース落としてきてbuildしてみて動かす・・・駄目。

しょうがないので上の様に素直なコードをクラス版と併記して動かしてみると素直バージョンの方ではちゃんと判定しやがってます・・・。

クラス内部ではメソッドごとにUErrorCode変数を作ってたのでもしかしてUErrorCode変数のアドレスにコンテキスト束縛してるかも?(ないない)と思ってメンバ変数にしてみるも駄目。

あとは怪しそうなのはucsdet_setText()に対しての文字列データ渡してる部分くらいしか・・・ということでucsdet_setText()をを呼び出してるメソッドを見直し。
最初の時点では以下の様になってました。

template <typename iteratorT>
void operator()(iteratorT head, iteratorT tail)
{
	std::vector<char> buffer(head, tail);

	UErrorCode error = U_ZERO_ERROR;
	ucsdet_setText(detector,
				   &*buffer.begin(), buffer.size(), &error);

	...
}

こんな感じでイテレータを受け取って内部でバッファに詰め込んでから改めてucsdet_setText()にながしこんでます。

で、以下の様に変更

void operator()(const char* head, size_t length)
{
	UErrorCode error = U_ZERO_ERROR;
	ucsdet_setText(detector,
				   head, length, &error);

	...
}

そうすると・・・動きやがったよ・・・

まぁ、大体その時点で想像はついてたんですが裏を取るためにICUのソースを読みにいきました。
まずはucsdet_setText()・・・

U_CAPI void U_EXPORT2
ucsdet_setText(UCharsetDetector *ucsd, const char *textIn,
    int32_t len, UErrorCode *status)
{
    if(U_FAILURE(*status)) {
        return;
    }

    ((CharsetDetector *) ucsd)->setText(textIn, len);
}

これだけです。
一応ucsdの具象クラスをucsdet_open()みて確認します。

    CharsetDetector* csd = new CharsetDetector(*status);

とやってるのでポリモルフィックではないようです。
安心してCharsetDetector::setText()を見にいきます。

void CharsetDetector::setText(const char *in, int32_t len)
{
    textIn->setText(in, len);
    fFreshTextSet = TRUE;
}

さらにメンバ変数に渡してました。
型を調べるとtextInはInputText型だそうです。
さらに追跡。

void InputText::setText(const char *in, int32_t len)
{
    fInputLen  = 0;
    fC1Bytes   = FALSE;
    fRawInput  = (const uint8_t *) in;
    fRawLength = len == -1? uprv_strlen(in) : len;
}

旅が終わりました・・・中でコピーしてucsdet_detect/ucsdet_detectAllに備えてるはず!!と最初に思ってたところは見事に参照してるだけでした・・・
要するに自分のクラスのコードでは・・・

  • std::vectorにデータをコピー
  • ucsdet_setText()に渡す
    • 中ではstd::vectorのメモリ位置を保存して帰る
  • メソッド抜けてstd::vectorが破棄される
  • detect/detectAll()するメソッドを呼び出す
    • detect/detectAll()のなかで既に無効なstd::vectorのメモリを参照して文字コード判定。

と言う愉快なことになっていた様です・・・
linuxwindowsで試してみてたんですがlinuxだとちゃんと動いてたんですが削除時に0xddで埋める、というwindowsデバッグモード用アロケータの効果が働いていたおかげだった模様。

まぁ、勝手に安全側に振ってあるだろう(内部でコピー持ってるはず)と思ってた自分が悪いんですが、ライブラリなんだから安全側に振っといてほしいってもんです・・・

型配列

複数の型をlistに束縛してnthで取り出してみる。
中身はlispちっくにconsセルでつなげてます。

#include <iostream>
#include <typeinfo>

struct nil_t
{};

template <typename... args>
struct cons_cell;

template <typename first, typename... rest>
struct cons_cell<first, rest...>
{
	typedef first car;
	typedef cons_cell<rest...> cdr;
};

template <typename last>
struct cons_cell<last>
{
	typedef last car;
	typedef cons_cell<nil_t> cdr;
};

template <>
struct cons_cell<nil_t>
{
	typedef cons_cell<nil_t> car;
	typedef cons_cell<nil_t> cdr;
};

template <typename ... args>
struct list;

template <typename first, typename... rest>
struct list<first, rest...>
{
	typedef cons_cell<first, rest...> type;
};

template <typename type_list, int number>
struct nth
{
	typedef typename nth<typename type_list::cdr, number-1>::type type;
};

template <typename type_list>
struct nth<type_list, 0>
{
	typedef typename type_list::car type;
};

int main()
{
	typedef list<int, char, short, long,
		double, float, unsigned int>::type types_list;

	std::cout << typeid(types_list::cdr::car).name() << std::endl;
	std::cout << typeid(nth<types_list, 5>::type).name() << std::endl;
	std::cout << typeid(nth<types_list, 6>::type).name() << std::endl;
	std::cout << typeid(nth<types_list, 7>::type).name() << std::endl;
	std::cout << typeid(nth<types_list, 8>::type).name() << std::endl;

	return 0;
}


$ g++ -g -Wall -std=c++0x -o type_list type_list.cpp

$ ./type_list | c++filt -t
char
float
unsigned int
cons_cell
cons_cell

てなかんじです。
うーん、nil_tは中にcar, cdr用意しといた方がよかったな・・・

variadic template事始め

C++0xでvariadic templateを使って可変数引数の数を数えるプログラムを組んでみる。
使ったのはg++ Ubuntu 4.3.2-1ubuntu12
オプションは
g++ -g -Wall -std=c++0x -o variadic_templ variadic_templ.cpp

#include <iostream>
#include <typeinfo>

template <typename... arg>
struct length;

template <typename arg, typename... rest>
struct length<arg, rest...>
{
	enum {
		value = 1 + length<rest...>::value
	};
};

template <typename arg>
struct length<arg>
{
	enum {
		value = 1
	};
};

int main()
{
	typedef length<int, short, char, long, unsigned int> type_list;
	std::cout << typeid(type_list).name() << std::endl;
	std::cout << type_list::value << std::endl;

	return 0;
}

最初はよー分からんかったが、id:faith_and_braveさんとこのコードを見てて不完全型宣言してから特殊化としてtemplate argumentが可変のものを用意すればいいのかなー?とやったら通った。
あとで規格の該当個所読んで裏取りしないとな・・・

しかしなれるまで苦労しそうだ・・・
lisp風連結リストだとわかりやすかったんだけどなぁ・・・

Y combinator

asin:4320122089 を読んでたら不動点演算子が出てきて組みたくなったのでJavaで久しぶりに書いてみた。

interface Command 
{
	public void exec(Command self, Object[] args);
}

public class YCombinatorTest
{
	public static void main(String args[])
	{
		int argValue = 10;
		if (args.length == 1)
			argValue = Integer.parseInt(args[0]);
			
		new Command() {
			public void exec(Command executor, Object[] args) {
				executor.exec(executor, args);
			}
		}.exec(new Command() {
				public void exec(Command self, Object[] args)
				{
					assert args.length == 2;

					int value = ((Integer)args[0]).intValue();
					assert value > 0;
					int contValue = ((Integer)args[1]).intValue();

					if (value == 1)
						System.out.println("value: " + contValue);
					else {
						self.exec(self, new Object[] {
                new Integer(value - 1),
                new Integer(value * contValue)});
          }
        }
      }, new Object[] {
        new Integer(argValue),
        new Integer(1) });
  }

}

オブジェクトファイルの奇妙なcall命令

http://lucille.atso-net.jp/blog/?p=603
これなんですが、たしか参照解決前の関数エントリのインデックスだったような・・・
ということで確認してみる。

まずはコンパイルの準備。
ヘッダファイル。

extern "C" void test_c_func();

void test_cpp_func();

extern "C" void test_inline_c_func()
{}

void test_inline_cpp_func()
{}

test.hとして保存。


んで本体。

#include "test.h"

int main()
{
	test_c_func();
	test_cpp_func();
	test_inline_c_func();
	test_inline_cpp_func();

	return 0;
}

test.cppとして保存

で、コンパイル

$ g++ -g -c test.cpp

そして逆アセンブル

$ objdump -dS test.o | c++filt -t

test.o:     file format pe-i386

Disassembly of section .text:

00000000 <_test_inline_c_func>:

void test_cpp_func();

extern "C" void test_inline_c_func()
{}
   0:   55                      push   %ebp
   1:   89 e5                   mov    %esp,%ebp
   3:   5d                      pop    %ebp
   4:   c3                      ret    
   5:   90                      nop    

00000006 <test_inline_cpp_func()>:

void test_inline_cpp_func()
{}
   6:   55                      push   %ebp
   7:   89 e5                   mov    %esp,%ebp
   9:   5d                      pop    %ebp
   signed char: c3                      ret    
   bool:        90                      nop    

0000000c <_main>:
   char:        55                      push   %ebp
   double:      89 e5                   mov    %esp,%ebp
   float:       83 ec 08                sub    $0x8,%esp
  12:   83 e4 f0                and    $0xfffffff0,%esp
  15:   b8 00 00 00 00          mov    $0x0,%eax
  a:    83 c0 0f                add    $0xf,%eax
  d:    83 c0 0f                add    $0xf,%eax
  20:   c1 e8 04                shr    $0x4,%eax
  23:   c1 e0 04                shl    $0x4,%eax
  26:   89 45 fc                mov    %eax,0xfffffffc(%ebp)
  29:   8b 45 fc                mov    0xfffffffc(%ebp),%eax
  2c:   e8 00 00 00 00          call   31 <_main+0x25>
  31:   e8 00 00 00 00          call   36 <_main+0x2a>
  36:   e8 00 00 00 00          call   3b <_main+0x2f>
  3b:   e8 00 00 00 00          call   40 <_main+0x34>
  40:   e8 bb ff ff ff          call   0 <_test_inline_c_func>
  45:   e8 bc ff ff ff          call   6 <test_inline_cpp_func()>
  4a:   b8 00 00 00 00          mov    $0x0,%eax
  4f:   c9                      leave  
  50:   c3                      ret    
  51:   90                      nop    
  52:   90                      nop    
  53:   90                      nop    
  54:   90                      nop    
  55:   90                      nop    
  56:   90                      nop    
  57:   90                      nop    
  58:   90                      nop    
  59:   90                      nop    
  5a:   90                      nop    
  5b:   90                      nop    
  5c:   90                      nop    
  5d:   90                      nop    
  5e:   90                      nop    
  5f:   90                      nop  

で、call部分の抜粋

  2c:   e8 00 00 00 00          call   31 <_main+0x25>
  31:   e8 00 00 00 00          call   36 <_main+0x2a>
  36:   e8 00 00 00 00          call   3b <_main+0x2f>
  3b:   e8 00 00 00 00          call   40 <_main+0x34>
  40:   e8 bb ff ff ff          call   0 <_test_inline_c_func>
  45:   e8 bc ff ff ff          call   6 <test_inline_cpp_func()>

inline化してるものはobjdumpでの逆アセンブル結果で関数名まで出てます。
問題はその上の4行分。

でobjdumpでいろいろみてたらそれっぽいのを見つけた。

$ objdump -r test.o

test.o:     file format pe-i386

RELOCATION RECORDS FOR [.text]:
OFFSET   TYPE              VALUE 
0000002d DISP32            __alloca
00000032 DISP32            ___main
00000037 DISP32            _test_c_func
0000003c DISP32            __Z13test_cpp_funcv


RELOCATION RECORDS FOR [.stab]:
OFFSET   TYPE              VALUE 
00000014 dir32             .text
00000020 dir32             .text
00000158 dir32             .text
00000164 dir32             .text
00000194 dir32             .text
000001d0 dir32             .text
000001dc dir32             .text
00000254 dir32             .text

リロケーション情報にあった。なんでか1ずつオフセットずれてるみたいだけど・・・

確認のため、実行ファイルにして付き合わせてみる。

まずは外部関数の用意

extern "C" void test_c_func()
{}

void test_cpp_func()
{}

test_link.cppとして保存。

で、コンパイルとリンク

$ g++ -g -c test_link.cpp
$ g++ -g -o test test.o test_link.o

そして逆アセンブル

$ objdump -dS test.exe | c++filt -t
(中略)
004012fc <_main>:
  4012fc:       55                      push   %ebp
  4012fd:       89 e5                   mov    %esp,%ebp
  4012ff:       83 ec 08                sub    $0x8,%esp
  401302:       83 e4 f0                and    $0xfffffff0,%esp
  401305:       b8 00 00 00 00          mov    $0x0,%eax
  40130a:       83 c0 0f                add    $0xf,%eax
  40130d:       83 c0 0f                add    $0xf,%eax
  401310:       c1 e8 04                shr    $0x4,%eax
  401313:       c1 e0 04                shl    $0x4,%eax
  401316:       89 45 fc                mov    %eax,0xfffffffc(%ebp)
  401319:       8b 45 fc                mov    0xfffffffc(%ebp),%eax
  40131c:       e8 4f 05 00 00          call   401870 <___chkstk>
  401321:       e8 ca 00 00 00          call   4013f0 <___main>
  401326:       e8 25 00 00 00          call   401350 <_test_c_func>
  40132b:       e8 26 00 00 00          call   401356 <test_cpp_func()>
  401330:       e8 bb ff ff ff          call   4012f0 <_test_inline_c_func>
  401335:       e8 bc ff ff ff          call   4012f6 <test_inline_cpp_func()>
  40133a:       b8 00 00 00 00          mov    $0x0,%eax
  40133f:       c9                      leave  
  401340:       c3                      ret    
  401341:       90                      nop
(後略)

スタック検査が挿入されてます。
あとcall 40が消えてる・・・

perlの多次元配列

perlの二次元配列の作り方がさっぱり - 誰か助けて orz - ir9Ex’s diaryの話題について反応。

ぶっちゃけperlなんてもう5, 6年まともに触ってないのでぜんぜん覚えてないのでもっときれいなやり方とかあったら誰か教えてください。

今回の件がcsvっぽいデータから2次元配列への格納ってことだったのでそれを踏襲して書いてみた。

まずはテストデータ、test.csv

1, 2, 3, 4, 5, 6, 7
21, 22, 23
31, 32, 33, 34, 35

で、コード本体、test.pl

#!/usr/bin/perl

open FILE, "<./test.csv" or die;

@result = ();
while (<FILE>)
{
    @columns = split /,/, $_;

    push (result, \@columns);
}

print $result[0][1] . "\n"; # = 2
print $result[1][2] . "\n"; # = 23
print $result[2][3] . "\n"; # = 34

で、実行結果

$ ./test.pl 
 32
 33
 34

はい、見事に失敗です。
どうも参照は配列自体ではなくてポインタで指し示してるような感じっぽい。
んじゃ配列コピーして参照渡したらどうか?と思ってぐぐってみたら角括弧でアレイのコピーになる - 西尾泰和のはてなダイアリーが見つかりました。

id:uskzさんが書いてた方法はこれを使ってるってことなのね。

ということでtest.pl書き換え。

#!/usr/bin/perl

open FILE, "<./test.csv" or die;

@result = ();
while (<FILE>)
{
    @columns = split /,/, $_;

    push (result, [@columns]);
}

print $result[0][1] . "\n"; # = 2
print $result[1][2] . "\n"; # = 23
print $result[2][3] . "\n"; # = 34

pushするときに角括弧でくくるようにしただけです。
で、実行結果。

$ ./test.pl 
 2
 23

 34

おっとっと、$result[1][2]の要素が改行削ってなかったのでおかしいことになってますが、多次元配列はうまく動いたようです。