2013年12月3日火曜日

bash-completion

softies projectの開発環境は Solarisと Ubuntuを使用しているが、先日、Ubuntuを 10.04→ 12.04に変えたところ、何やらいろいろと拙いことが発生し始めた。

makeが通らない

ちょっとした実験をする Main.cというソースがあったとしよう。たとえば次のようなもの。
#include 

int main() {
 printf("Hello, world.\n");
 return 0;
}

これだけなら、わざわざ makefile(や build.xml, pom.xml)を作らなくとも、
$ make Main
cc     Main.c   -o Main

の様にビルドが通る。これはデフォルトで定義されているサフィックスルールだけで何とかなる場合だからだ。

しかし、Ubuntu 12.04にしたところ、
$ make Main
make: *** ターゲット 'Main' を make するルールがありません. 中止.

と表示されてしまう。

makeコマンドで補完ができない

まだ可笑しい所がある。
シェルでmake Mのあと、Tabキーを押しても補完されない。
make Main.cが出ることを期待していたのだが。
使っているシェルが悪いのかと psしてみたが、bashである。
試しに echoとか、lsとか、catとかのあとに Mに続けて Tabキーを押すとちゃんと補完される。
Tabキーの接触不良でもないようだ。
また誰か余計な機能を発明したようだ。

理由は bash-completion

ネットで調べてみると出てきたのは bash-completionという機能。
makeや gitなど、使用するコマンドによって補完の推測方法を変えているようだ。
makeでは、makefileのターゲットを調べ、あるものしか入力できない。
makefile無しの場合のターゲットの推測までは頭が回ってないようだ。
中途半端なヤツ。
bas-completionが有効になってるかどうかは、次のように complete -pを実行して completeルールがゾロゾロと出てくるかどうかで確かめることができる。
$ complete -p
 :
 :
complete -F _route route
complete -o default -o dirnames -F _mount mount
complete -F _badblocks badblocks
complete -F _filedir_xspec lyx
complete -F _filedir_xspec rgvim
complete -F _filedir_xspec timidity
complete -F _filedir_xspec dvitype
complete -F _filedir_xspec dviselect
complete -F _filedir_xspec xdvi
complete -o default -F _longopt uniq
complete -F _root_command sudo
complete -F _command tsocks
complete -a unalias


bash-completionを無効にする

この機能を無効にするには、
sudo apt-get remove bash-completion

とやってアンインストールすれば良いのだが、それだと他のユーザまで影響してしまうので、つぎのようにしてローカルにアンインストールすれば良い。
$ complete -r

アンインストールがうまく行っていれば、
$ complete -p

でなにも出てこない。

bash-completionを無効にすると、makefileにないターゲットや makefile自体が無い状態での補完ができるようになる。

なお、makeでは無効にしたいが、gitでは有効にしておきたいということもできるようだ。



先輩、暮らす図って何ですか?

2013年12月2日月曜日

入れ子関数 その2 (nested function 2)

前回の入れ子関数 その1では、1つのスコープで同一名称の関数を定義してもエラーにはならないが使えないことが確認されたが、入れ子関数に関してはローカル変数にアクセスできるという便利な機能についても触れておきたい。

ローカル変数へのアクセス

次のプログラムは、入れ子関数 getが、その外側の mainのローカル変数 vの値を参照する例である。
#include 

int main() {
 int v = 5;
 int get() { return v; }
 printf("%d\n", get());
 return 0;
}

実行してみよう。
$ make NestedFunctionExample11
cc     NestedFunctionExample11.c   -o NestedFunctionExample11
$ ./NestedFunctionExample11
5

入れ子関数が、関数の中の単なるブロックであるかのように変数にアクセスできることがわかる。

入れ子関数のポインタ

関数のポインタを渡して、その渡した先で vにアクセスできるだろうか。
#include 

typedef int (*function)();

void sub(function f) {
 printf("%d\n", f());
}

int main() {
 int v = 5;
 int get() { return v; }
 sub(get);
 return 0;
}

$ make NestedFunctionExample12
cc     NestedFunctionExample12.c   -o NestedFunctionExample12
$ ./NestedFunctionExample12
5

subの中から呼び出されている関数 fが mainのローカル変数 vにアクセスできている。

subは mainのスタックフレームにアクセスできるだけなのか、自身のフレームも持っているのだろうか。
#include 

typedef int (*function)();

void sub(function f) {
        printf("%d\n", f());
}

int main() {
        int v = 5;
        int get() {
                int u = 3;
                printf("%d\n", u);
                return v + u;
        }
        sub(get);
        return 0;
}

$ make NestedFunctionExample13
cc     NestedFunctionExample13.c   -o NestedFunctionExample13
$ ./NestedFunctionExample13
3
8

これを見ると、自身のスタックフレームを持った上で、その外側にある関数のスタックフレームにもアクセスできるようだ。

外側の関数から復帰後

入れ子関数を定義した関数から復帰してしまったらどうか。
#include 

typedef int (*function)();

void sub(function f) {
 printf("%d\n", f());
}

function getFunction() {
 int v = 899;
 int get() { return v; }
 return get;
}

int main() {
 function f = getFunction();
 sub(f);
 return 0;
}

多分外側の関数のスタックフレームはもう無いからアクセスできないはず。
$ make NestedFunctionExample14
cc     NestedFunctionExample14.c   -o NestedFunctionExample14
$ ./NestedFunctionExample14
3
902

あれ、うまくアクセスできてしまった。

実はこれ、GCC 4.6.0 on SunOS 5.11 (x86)環境ではうまく行ってしまう。(様に見える)

同じコードを GCC 4.6.3 on Ubuntu 12.04 (x86)でやってみると。
$ make NestedFuctionExample14
cc     NestedFuctionExample14.c   -o NestedFuctionExample14
$ ./NestedFuctionExample14 
134513727

ちゃんと滅茶苦茶な値になった。(うまく行かなかったことを安心しているようで妙な表現だが)
Solarisでうまく行ったように見えたのはスタックフレームの内容が残っていたからで、いつもこう行くとは限らない。

たとえば、関数ポインタを取得してから呼び出すまでの間にスタックを使用するなにか別の関数を呼んでしまうと壊れてるはず。
#include 

typedef int (*function)();

void sub(function f) {
 printf("%d\n", f());
}

function getFunction() {
 int v = 899;
 int get() {
  int u = 3;
  printf("%d\n", u);
  return v + 3;
 }
 return get;
}

int main() {
 function f = getFunction();
 printf("Hello, world.\n");
 sub(f);
 return 0;
}

この様に、printfを入れてみよう。
$ make NestedFunctionExample15
cc     NestedFunctionExample15.c   -o NestedFunctionExample15
$ ./NestedFunctionExample15
Hello, world.
Segmentation Fault (core dumped)

値が可笑しくなるだけでなく、アクセス違反が生じてしまった。

2013年12月1日日曜日

入れ子関数 その1 (nested function 1)

例外機構例外機構 その2で紹介した言語 Cでの例外処理の機構はマクロと関数を組み合わせて実現しているが、制約が多くまだ実験の初期段階の域を出ない。

制約のうちの幾つかは finally節に関するものである。
  • finallyは省略できない。
  • 同一スコープの中では finallyが2回以上記述できない。
これは、gccの入れ子関数(nested function)機能の使い方に由来する。

同一スコープの中で複数の入れ子関数を定義した場合になにが起こるか、GCCのオンラインドキュメントNested Functionsで調べてみたが、書かれていない。

そこで、幾つかの実験を行って見た。
使用した環境はつぎの通り。

  • GCC 4.4.3 on Ubuntu 4.4.3-4ubuntu5.1 (x86)
  • GCC 4.6.0 on SunOS 5.11 (x86)

異なるスコープで同じ名称の関数を定義した場合の振る舞い

まず同一スコープで複数回定義した場合の実験の前に、同じ名称の関数を異なるスコープで定義しそれを呼び出した場合になにが実行されるかを試してみる。

次のコードの関数 subが実験コードである。
外側と内側のスコープでは各々 "f(1)"、"f(2)"を表示する関数 fを定義している。
その後、fという名前の関数を呼び出すと呼び出したスコープに一番違い f(2)が呼び出されると思われる。

#include <stdio.h>

void sub() {
 void f() { printf("f(1)\n"); }
 {
  void f() { printf("f(2)\n"); }
  f();
 }
}

int main() {
 sub();
 return 0;
}

ビルドして、実行してみる。
$ make NestedFunctionExample1
cc     NestedFunctionExample1.c   -o NestedFunctionExample1
$ ./NestedFunctionExample1
f(2)

予想通り、f(2)が表示された。
念のため、関数 subを書き換えて、内部の定義が無くとも外部の定義が呼ばれることを確認しておく。
void sub() {
 void f() { printf("f(1)\n"); }
 {
  f();
 }
}

$ make NestedFunctionExample2
cc     NestedFunctionExample2.c   -o NestedFunctionExample2
$ ./NestedFunctionExample2
f(1)

期待通り、外側のスコープで定義した入れ子関数が呼び出された。
内側のスコープで 関数 fを定義する前に fという名前で呼び出しを行なおうとしたらコンパイルは通るのか、通ったとしたら何が呼ばれるのかを試してみる。
void sub() {
 void f() { printf("f(1)\n"); }
 {
  f();
  void f() { printf("f(2)\n"); }
  f();
 }
}

$ make NestedFunctionExample3
cc     NestedFunctionExample3.c   -o NestedFunctionExample3
$ ./NestedFunctionExample3
f(1)
f(2)

コンパイルはエラーにならなかった。自動変数の場合もスコープが違えば同じ名前でもちゃんと区別されるので期待通りだ。
内側の定義を行う前と後では呼び出される関数が異なることが判明した。
これは自然な感じがする。
前方参照を用いて、外側の関数定義を呼び出しより後に定義したらコンパイル、実行はどうなるだろうか。
void sub() {
 auto void f();
 {
  f();
  void f() { printf("f(2)\n"); }
  f();
 }
 void f() { printf("f(1)\n"); }
}

$ make NestedFunctionExample4
cc     NestedFunctionExample4.c   -o NestedFunctionExample4
$ ./NestedFunctionExample4
f(1)
f(2)


おお、すばらしい。

同一スコープで同じ名称の関数を定義した場合の振る舞い

いよいよ、同じ関数名で定義したらどうなるか。
void sub() {
 void f() { printf("f(1)\n"); }
 f();
 void f() { printf("f(2)\n"); }
 f();
}

$ make NestedFunctionExample5
cc     NestedFunctionExample5.c   -o NestedFunctionExample5
NestedFunctionExample5.c: In function ‘sub’:
NestedFunctionExample5.c:11:7: error: redefinition of ‘f’
NestedFunctionExample5.c:9:7: note: previous definition of ‘f’ was here
make: *** [NestedFunctionExample5] Error 1

関数fの再定義はコンパイルエラーとなる。
でも諦めない。なぜなら例外機構では try~catch~finallyを 2個並べてもコンパイルは通っているのだから。
1個目の定義のあと、宣言を入れてみよう。
void sub() {
 void f() { printf("f(1)\n"); }
 f();
 auto void f();
 void f() { printf("f(2)\n"); }
 f();
}

vi NestedFunctionExample6.c
$ make NestedFunctionExample6
cc     NestedFunctionExample6.c   -o NestedFunctionExample6
$ ./NestedFunctionExample6
f(2)
f(2)

コンパイルは通った。
だが、どちらも2個目の定義が呼ばれていて、1個目の定義は無視されている。
これが、例外機構での制約の原因だ。

最初の呼び出しでは1個目の定義のが呼ばれても良いような気がするのだが。

gccのソースを見ていないので憶測だが、同一スコープで同じ名前の関数を定義しようとすると1つ前の実験でわかるように名前の衝突が起きてエラーになるが、
宣言を書けば衝突は回避されるようだ。
しかし、定義の前でも後でも2個目に定義した関数が呼ばれているところを見れば、単純に関数定義を上書きしているのだろう。

宣言は前方参照できるはずなので、定義を呼び出しを逆にしても結果は同じである。
void sub() {
 void f() { printf("f(1)\n"); }
 f();
 auto void f();
 f();
 void f() { printf("f(2)\n"); }
}

$ make NestedFunctionExample7
cc     NestedFunctionExample7.c   -o NestedFunctionExample7
$ ./NestedFunctionExample7
f(2)
f(2)

当然だけど、次の例も再定義でエラーになる。
void sub() {
 auto void f();
 f();
 void f() { printf("f(1)\n"); }
 f();
 void f() { printf("f(2)\n"); }
}

$ make NestedFunctionExample8
cc     NestedFunctionExample8.c   -o NestedFunctionExample8
NestedFunctionExample8.c: In function ‘sub’:
NestedFunctionExample8.c:13:7: error: redefinition of ‘f’
NestedFunctionExample8.c:11:7: note: previous definition of ‘f’ was here
make: *** [NestedFunctionExample8] Error 1

これまで定義が2個の例を示したが、次のように3個以上でも同じことである。
void sub() {
 auto void f();
 f();
 void f() { printf("f(1)\n"); }

 auto void f();
 f();
 void f() { printf("f(2)\n"); }

 auto void f();
 f();
 void f() { printf("f(3)\n"); }

 auto void f();
 f();
 void f() { printf("f(4)\n"); }
}

$ vi NestedFunctionExample9.c
$ make NestedFunctionExample9
cc     NestedFunctionExample9.c   -o NestedFunctionExample9
$ ./NestedFunctionExample9
f(4)
f(4)
f(4)
f(4)

以上の結果から、定義と宣言を分けると同一スコープに同じ名称の関数定義を記述することはできるが、最後の定義のみが有効となることが判明した。

これでは現在の例外機構の案では実用にはならない。

gccのバージョンが変わったら挙動が変わる可能性もある。

2013年11月13日水曜日

非同期シグナルセーフ その2 (asynchronous signal safe stderr library -- experimental)

シグナルハンドラの中から安全に呼び出すことのできるエラー出力関数のセットを紹介する。

printfのような書式指定機能は付いていないため、幾つかの関数をバラバラに並べて呼び出さなければならない。

実は、softiesプロジェクトで使われている mallocライブラリ内部のエラーハンドリングに用いているため、各々の関数には static宣言が付いているが、掲載したものは staticは外してある。(この mallocライブラリについては後日紹介する機会があるかも知れない。)

クラス名などのプレフィックスは付いてないため、使う場合には関数名が干渉しないか注意しなければならない。

コンパイルを通すには unistd.hのインクルードが必要となる。

例によって十分なテストは行われていないため、動作は保証しない。


改行出力

void newline() {
 write(STDERR_FILENO, "\n", 1);
 fsync(STDERR_FILENO);
}


文字列出力

NUL文字で終端された char配列 stringを出力する。
void printString(char* string) {
 if (string == null)
  string = "(null)";

 int length = 0;
 while (string[length] != '\0')
  ++length;
 write(STDERR_FILENO, string, length);
}


バイナリデータ中の ASCII文字列を抽出して出力

char配列 stringの lengthで指定されたバイト数分を出力する。
ASCII文字で無い値はスキップされる。
void printAsciiLetters(char* string, int length) {
 if (string == null)
  string = "(null)";

 int index = 0;
 while (index < length && string[index] >= 0x20 && string[index] < 0x7f)
  ++index;
 write(STDERR_FILENO, string, index);
}

整数出力

符号ありの整数しか扱えない。 int型が符号を含めて 16桁を超える処理系では正しく動作しない。
void printInteger(int value, int length) {
 char buffer[16];
 if (length > 16)
  length = 16;
 int index = 0;
 boolean negative = false;
 if (value < 0) {
  negative = true;
  value = -value;
 }
 do {
  buffer[15 - index++] = (value % 10) + '0';
  value /= 10;
 } while (value != 0 && (length == 0 || index < length));
 if (negative && index <= 15)
  buffer[15 - index++] = '-';
 while (index < length)
  buffer[15 - index++] = ' ';
 write(STDERR_FILENO, &buffer[15 - index + 1], index);
}

ポインタ出力

ポインタを '0x'が先行する 16進数で出力する。 ポインタが 0xを含めて 16桁を超える処理系では正しく動作しない。
void printPointer(void* pointer, int length) {
 char buffer[16];
 long value = (long)pointer;
 if (length > 16)
  length = 16;
 int index = 0;
 do {
  int d = (int)(value & 0xf);
  buffer[15 - index++] = (d < 10)
   ? (d + '0')
   : (d - 10 + 'a');
  value >>= 4;
  value &= 0xfffffff;
 } while (value != 0 && (length == 0 || index < length));
 buffer[15 - index++] = 'x';
 buffer[15 - index++] = '0';
 while (index < length)
  buffer[15 - index++] = ' ';
 write(STDERR_FILENO, &buffer[15 - index + 1], index);
}

2013年11月12日火曜日

非同期シグナルセーフ

ライブラリ関数の多くは非同期なシグナルハンドラの中で使用できない。

printfもその一つ。
プログラムのエラーをシグナルで捕えても、そこで printfなどのライブラリを用いてメッセージを出力することはできない。

たしか System V Release 3.0が出た頃はまだそのような制約は無かったと思う。
その頃は平気で printfを使っていた記憶があるが、いつの日かそのような使い方は禁止されている。
そこで手元にある文献を幾つか調べてみた。

Marc J.Rochkind著、福崎俊博訳
『UNIXシステムコール・プログラミング』
アスキー出版局 1987年(原書は1985年)

第8章 シグナル
シグナルハンドラのクリーンアップ時に fprintfする例が示されている。
また、longjumpの例もある。
シグナルが到着したときに単にフラグをセットし、プログラムがレディになってからそれをチェックする賢いトリックを使うのは悪いテクニックとあり、これは現在の常識とは反対である。


AT&Tユニックス パシフィック発行
『UNIX System V プログラマ・リファレンス・マニュアル 第2版 リリース 3.0』
共立出版 1986年(原書も1986年)

signal, sigset系システムコールの説明があるが、シグナルセーフについては書かれていない。
同シリーズの UNIX System V プログラマ・ガイド リリース3.1は調べていない。


塩谷修著
『実用UNIXシステムプログラミング』
日刊工業新聞社 1986年

第13章 シグナル
SIGINT, SIGHUP, SIGSEGV, SIGFPE, SIGPIPE, SIGALARM, SIGCLDについて、各々 printfを使った例が示されている。
第18章 SetJmp LongJmp
シグナルハンドラから longjmpする例が示されている。


David A.Curry著、アスキー書籍編集部監訳
『UNIX Cプログラミング』
アスキー出版局 1997年(原書は1989年版)

8章 シグナル処理
シグナルのリセット、システムコールの再スタートに関する問題が示されている。
4.2BSDのバークレーUNIXのシグナル機構の説明も示されている。
しかし、例示されているハンドラの内部では printfが使われており、さらにはハンドラの中から longjumpを行う例まで扱われている。


M.R.ホートン著、長尾高広訳
『ポータブルCプログラミング』
トッパン 1990年(原書も 1990年)

12章 シグナル処理ルーチンの設定
元のシグナルのメカニズムには信頼性上の問題があったが、4.2BSD, ANSI C, POSIX, System V release 3では何らかの改良が施されたことが書かれている。
ANSI Cでは、シグナルハンドラでの longjumpの動作が未定義となったことは書かれている。


Bil Lewis, Daniel J.Berg共著、岩本信一訳
『マルチスレッドプログラミング入門』
アスキー出版局 1996年(原書も 1996年)

第6章 オペレーティングシステムの問題
非同期に関する安全性のところで、非同期シグナルの問題をmallocを例に説明している。
非同期シグナル安全な関数の一覧は掲載されていないが、マニュアルを参照するように書かれている。
第10章 プログラミング事例
シグナルハンドラでは、非同期シグナルのハンドリングは安全な sigwaitを使った例を示している。


David R.Butenhof著、油井尊訳
『POSIXスレッドプログラミング』
アジソン・ウェスレイ 1998年(原書は 1997年)

6.6 シグナル
まず、スレッドとシグナルの関係について説明している。
その後で、スレッドコード内部で非同期シグナルを扱うには必ず sigwaitを使用するように書かれている。
ここでは、非同期シグナル安全という説明はされていない。
その後、非同期シグナル安全についての説明と、その関数の一覧が示されている。


ビル・ヒルズ+ダニエル・バーグ著、岩本信一訳
『Pスレッドプログラミング』
プレンティスホール 1999年(原書は1998年)

10章 シグナル
非同期セーフ
mallocの例を挙げて非同期シグナルの問題を提起し、非同期シグナルセーフのカテゴリがあることが説明されている。
非同期シグナルセーフの関数一覧は示されず、ベンダのドキュメントを参照するように書かれている。


こう見ると、スレッドプログラミングが導入されるようになってからの制約の様である。
元々シグナル処理には再入性などの問題があったが、スレッドプログラミングでは mutexを用いた排他処理などが組み込まれていて、シグナルハンドラではそれがうまく機能ささせられないのである。
ライブラリ関数はほとんど使用できないが、システムコールはファイルアクセスであれ、ソケット通信であれ使用できる。



国会議員には国民の先頭に立って震災の復興のために尽力して頂きたい。
綸旨を奏請するのはあらゆる手を尽くしてからだ。

2013年8月12日月曜日

マルチスレッド その2 (multi thread 2)

pthreadでは、スレッドごとにデフォルトサイズのスタックが割り当てられるが、このサイズは pthread属性によって変更することができる。
デフォルトサイズはシステムで定義された値で、OSごとに経験的に決められた一般的なスレッドに充分な値になっている。

しかし、より多くのスタックを必要とする場合は大きな値にしなければならない。

また、使用するアプリケーションがスタックをあまり消費しないことが解かっていて、多くのスレッドを使用したい場合には積極的にスタックサイズを小さくしたい。
ただし、1スレッドあたりのスタックの最小値が決められていて、limits.hファイルの中にある記号名 PTHREAD_STACK_MINに定義されているのでこれを下回らないようにしなければならない。

Solaris 11の場合

Solarisのデフォルトサイズは 1Mバイトである。(OpenMPを使った場合は 32bitシステムでは 4Mバイト、64bitシステムでは 8Mバイト)

スタックサイズを指定しなければならないようなケースは多くないはずである。
スタックのサイズをオーバした場合、なにも警告することなしに隣接スレッドを破壊することが Solarisのドキュメントには明記されている。
値は試行錯誤を経て決められるようになっているようだ。

mprotectを呼び出してレッドゾーンを付加しておけば、オーバした場合にセグメント例外が発生するはずである。
(OpneMPでは-xcheck=stkovf を付けると良いと示されているが、gccを使用した場合にどうするかは不明。)


PTHREAD_STACK_MINを使用したプログラムを gcc 4.6.0でコンパイルしようとすると、limits.hをインクルードしてあるにも係わらずつぎのようなエラーが出てしまう。
error: ‘PTHREAD_STACK_MIN’ undeclared (first use in this function)


ヘッダファイルを調べると、次の条件を満たしていなければならないことが判る。
#if     defined(__EXTENSIONS__) || (_POSIX_C_SOURCE >= 199506L)



そういえば、『鎌坪商店これにて閉店。ちょわーん。』してから半年近く経った。
夏休み子供電話相談室くらいはと期待してたのだが…。

2013年8月1日木曜日

マルチスレッド (multi thread)

当プロジェクトのクラスはマルチスレッドのためのコードが含まれている。
しかし、実装が不完全で限られた使い方でしか動作は保証されていない。
取り分け、シングルトンオブジェクトの排他制御がまともに出来ていないのと、スレッドごとに異なる値を持たなければならない変数が静的変数になっていてスレッド間で共有されているのが課題。


mutexの初期化

オブジェクト固有の mutexは、そのオブジェクトを生成するときに初期化を行えばよい。

問題はシングルトンの場合だ。
static boolean initialized = false;

struct Something object;

  :

 if (!initialized) {
  /* object will be initialized. */
 }

このようなコードは、単一のスレッドから呼び出す場合にのみ動作が保証される。

複数スレッドから同時に呼ばれる可能性がある場合、initializedのチェックは役に立たない。

そこで、mutexにより排他制御を行って見よう。(pthreadの場合。)

#include 

static boolean initialized = false;
static pthread_t mutex;

struct Something object;

  :

 pthread_mutex_lock(&mutex);
 if (!initialized) {
  /* object will be initialized. */
 }
 pthread_mutex_unlock(&mutex);

この処理が呼ばれる度に mutex操作が発生するとオーバヘッドが大きくなるので、それを回避する必要がある場合には外側にもう一段 initializedのチェックを入れると良い。

いづれにしてもこの mutexは事前に初期化されていなければならない。

特定のアプリケーションであれば、mutex(または objectの)初期化が完了してから複数スレッドを起動するように設計すれば十分であるが、ライブラリやフレームワークなどのミドルウェアの場合そのような制約はあてにならない。

そのようなミドルウェアは、mutex(または objectの)初期化を何らかの初期化関数として提供することになるが、仮にアプリケーションが初期化関数を呼んだ後にしかスレッドを起動しなかったとしてもまだ不十分である。

アプリケーションだけでは初期化の順序を守れないケースがあるからである。


2011年のことだったと思う。開発していた組み込み製品のデバッグ担当者から彼らの上司に『main関数にブレークポイントを設定したのに効きません。』とか『main関数よりも前に関数が呼ばれます。』という電話連絡が入った。

組み込みの開発では言語 Cを使用することが多いが、そのプロジェクトは C++が使われていたのだ。

Cでは、静的変数や外部変数を初期化するときに、main関数を呼ぶ startupルーチンの中から 変数領域の初期化が行われる。(bss領域はゼロフィルされ、data領域は初期値がコピーされる。)

ユーザのコードが mainよりも前に走り始めることは無い。
しかし、C++では定数で初期化するだけでなく、コンストラクタを起動したり、関数の結果を初期値に出来る。
#include 
#include 

std::string message("Hello, world.");

int len = message.length();

int main() {
        std::cout << message << std::endl;
        std::cout << len << std::endl;
        return 0;
}
C++のときだけではない。gccの拡張機能である constructor属性を関数に指定すると、main関数よりも前に実行される。
アプリケーションが main関数よりも前に何もしないつもりでも、使用しているライブラリが何かスレッドを起こして、なにかするかも知れない。

pthread_once

pthreadの場合、pthread_onceを使えば、そのプロセスでただ一度だけ処理を実行することが保証される。
mutexの初期化を気にせずにシングルトンを実現できる。
#include 

struct Something object;

void initializer() {
 /* object will be initialized. */
}

 :

 pthread_once(initializer)
 /* use object */

これで簡単確実にオブジェクトを初期化することができるようになった。

PTHREAD_MUTEX_INITIALIZERによる mutexの初期化

多くの場合のオブジェクトの初期化は pthread_onceでうまくいくが、pthread_onceでロックしてしまう場合がある。
softiesプロジェクトでは、Objectクラスはは Classクラスのインスタンスを持っていてこれは Objectを使用する前に初期化されなければならない。
その Classクラスは Objectクラスのサブクラスであり、Classクラスを初期化する場合にはそれに先立って Objectクラスを初期化しなければならない。
このように循環している場合、pthread_onceではロックするのである。
勿論、Object用の初期化関数と Class用の初期化関数は分けてあり、pthread_onceは各々別々に呼び出されるが、その中で相手側を呼び出すことになる。 (アプリケーションから明示的にフレームワーク、ライブラリの初期化関数を呼び出すようにしていないため、順序固定で初期化されるようなコーディングは行っていない。)
Objectの pthread_onceで指定された初期化関数の中から、Classの pthread_onceで指定された初期化関数を呼ぶところまでは何の問題もない。 (順序が逆になってもそうだ。)
ところが、その Classの初期化関数から再び Objectの pthread_onceを呼び出すと pthread_onceの呼び出しから復帰しなくなる。
そこで、何とかもう一度 mutexを使う方法を検討してみた。 普段、 mutexを使用するときは pthread_mutex_init関数を使用していた。そうしなければならないと思っていた。
しかし、ドキュメントをよく読むと、PTHREAD_MUTEX_INITIALIZERという初期値があるではないか。
つぎのように定数として mutexを初期化できる。
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
これなら、main関数よりも前に startupルーチン関連以外の処理が走るよりも前に mutexが使えるようになる為、最初のような initializer変数と組み合わせてオブジェクトの初期化を行うことができる。
Classクラスと Objectクラスのような初期化が循環していても、どちらの初期化から先に行おうとしたときでもロックすることなく処理を行うことができるようになった。
ただし、ヒープメモリから割り当てた領域に mutexを配置したり、pthread_mutexattr_tを指定した初期化したい場合には PTHREAD_MUTEX_INITIALIZERは使えない。
数ヶ月前から、電車のディスプレイに中日新聞ニュースが流れないことがしばしばあり、折角μチケット買ったのに残念な思いをする。

2013年6月30日日曜日

Objectクラスと同期プリミティブ その3

条件変数 (condition variable)

あるスレッドで変数を更新し、それを別のスレッドで待って処理を行うために Objectクラスのモニタ機構を用いることができる。
このモニタによって更新の通知と待ち合わせが行われる変数を条件変数と呼ぶ。

変数の更新を待つスレッドはつぎのように記述される。
 Object_lock(object);
 Object_wait(object, 0L);
 // objectに関連付けられた変数を読み出す
 Object_unlock(object);

Object_lockによりロックが行われると Object_waitにより更新されたことの通知を待つ。
この間一時的にロックは自動解除される。そうしないと、更新する側もロックを取得しようとするため、更新動作に入れなくなる。
重要なのは、再び waitを行っていたスレッドがロックを獲得した状態で Object_waitから復帰するということである。
Object_waitの 2つ目の引数は待ち時間の上限をミリ秒単位の long値で指定する。 0Lを指定した場合は通知が行われるまで無限に待ち続ける。
条件変数は任意の変数で構わないが、objectが Objectのサブクラスであれば、条件変数はそのクラスのメンバとするのが良い。
変数を読み書きする処理をそのクラスの関数にする事で処理の詳細を隠蔽し、併せてその関数の中でロックも行うようにすることができる。
ロック操作を利用者側に任せるのはロック漏れの原因となる。
変数を読み出している最中に更なる変数の更新が行われないようオブジェクトをロックしなければならない。

変数を更新するスレッドはつぎのように記述される。
 Object_lock(object);
 // objectに関連付けられた変数に書き込む
 Object_notify(object);
 Object_unlock(object);

Object_lockと Object_unlockで囲まれた中で変数を更新し、Object_notifyで objectで何かが起こることを待っているスレッドを再開させる。
最後に Object_unlockでロックを解放する。
Object_lockを行おうとしたときに、他のスレッドが変数に書き込み中だったり、読み出し中のものがあればそれらが完了するまで待たされる。
objectを待っているスレッドが複数あるとき、Object_notifyはその中の 1個のスレッドだけを再開させる。
どのスレッドが再開されるかは処理系の実装依存である。最初に待ち状態に入ったものが解除されることを期待するようなコードを書いてはならない。
objectを待っている複数のスレッド全てを再開させるには Object_notifyの変わりに Object_notifyAllを使用する。

なお、softiesプロジェクトでは Object_lockは必ずロックを獲得した状態で復帰する。
ロックが獲得できるかどうか試してみる tryLockの仕組みは提供していない。
他のスレッドから割り込まれてキャンセルされた場合例外をスローすることを検討されているが、現在のところ例外機構は実験段階であり、キャンセルは行われない。



アンタって少な目。 半分の整髪。

2013年5月23日木曜日

Objectクラスと同期プリミティブ その2

オブジェクトのロック

オブジェクトクラスは、それ自身をロックすることができる。

下記のように lock, unlockで囲むことにより、そのオブジェクトをロックすることができる。
Object_lock(object);

// 相互排他が必要な処理

Object_unlock(object);

オブジェクトがロックされている部分にはは唯一つのスレッドしか入ることができない。
1つのスレッドがオブジェクトをロックしている間に別のスレッドが lockを行おうとすると、そのスレッドは待ち状態になる。
ロックしているスレッドが unlockを実行することにより、待ち状態になっていたスレッドがロックを獲得することができる。
これにより複数のスレッドが同時にオブジェクトにアクセスすることによる競合を防止(相互排他)することができる。

ロック待ち状態のスレッドが複数個あった場合、ロックを獲得していたスレッドがロックを解放したことによりロックを獲得するするスレッドがどれになるかは不定である。(待ち状態に入った順にロックを獲得するとは限らない)
ロックを解放したスレッドが直ちにロック待ちに入るような場合、いつも特定のスレッドだけがロックを取得し、いつまでたっても獲得できないスレッドが生じる可能性があることに留意しなければならない。

複数範囲の相互排他

ロックは1つの箇所だけではなく、1つのオブジェクトに対して複数個所で相互排他を行うこともできる。
たとえば Objectクラスのサブクラスである Dataクラスが、次のように read関数と write関数に相互排他を行った場合、readと writeが複数スレッドで同時に実行することはない。
void Data_read(Data this) {
 Data_lock(this);
 // read処理
 Data_unlock(this);
}

void Data_write(Data this) {
 Data_lock(this);
 // write処理
 Data_unlock(this);
}


複数オブジェクトの間の関係

同じ read, write関数であっても、Dataオブジェクトが異なるものの間では排他は発生しない。
たとえば、おなじ Dataクラスの 2個のオブジェクト data1, data2があったとして、data1が read中に別スレッドで data2が writeするようなことはできる。

他の資源の排他

オブジェクトと任意の資源を組み合わせることにより、ロックしたデータ以外の資源の排他制御も行うことができる。
たとえば次の例は、char配列 arrayは自身をロックする仕組みを持たないが、lockObjectと組み合わせることにより相互排他を行うことができる。
arrayにアクセスするときは lockObjectのロックを取得し、アクセスが終わったときにロックを解放すれば良い。
struct Array {
 Object lockObject;
 char array[128];
};


注意事項

注意が必要なのは、unlockを確実の行うのはプログラマの責任である。
lockされている期間に何の配慮も無く例外を送出したり longjmpを行った場合 unlockされなくなり、閉塞状態に陥る。
また、Objectクラスの初期化が完了するまでは排他制御は保証されない。これは、コンストラクタの中で自オブジェクトをコールバック登録することなどにより、コンストラクタ完了前にコールバックされるような場合に問題が発生する。



最近、開発室の床に転がって眠ってしまい、気づくと朝になってることがある。
倉庫から梱包用エアクッションを持ってきて枕代わりにしていたが、先週日本製枕を買ってきた。

2013年5月16日木曜日

Objectクラスと同期プリミティブ その1

softiesプロジェクトのクラスの基底クラスである Objectクラスについて説明する。
Javaのようにすべてのクラスの基底とするかは未決定であるが、同期プリミティブや等価性の判定の仕組みなど、オブジェクトが持っていると都合のよい機能が提供されることになる。

機能一覧

つぎに示すのはインターフェースである。
インターフェースの構造についてはインスタンス関数のテーブルを参照。
typedef struct interface_lwd_lang_Object {
        lwd_lang_Class class_instance;
        void* (*interface_of)(char* name);

        void (*finalize)(lwd_lang_Object this);
        lwd_lang_Class (*getClass)(lwd_lang_Object this);
        int (*hashCode)(lwd_lang_Object this);
        boolean (*equals)(lwd_lang_Object this, lwd_lang_Object object);
        void (*lock)(lwd_lang_Object this);
        void (*unlock)(lwd_lang_Object this);
        void (*wait)(lwd_lang_Object this, long long timeout);
        void (*notify)(lwd_lang_Object this);
        void (*notifyAll)(lwd_lang_Object this);
        lwd_lang_String (*toString)(lwd_lang_Object this);
}* interface_lwd_lang_Object;


幾つか Javaのそれを真似ている。


finalizeはインスタンスを破棄するときに呼ばれる。
Javaではただ呼ばれるだけで、実際のインスタンスのメモリは別途コレクタにより回収される。
softiesでは、finalizeの中で自オブジェクトおよび自オブジェクトとライフサイクルをともにするオブジェクトのメモリを解放しなけれならない。


hashCodeと equalsは同価性の判断に使用される。
ただし、Objectクラスでは equalsは同一性の評価を行う。
同一性は2つのインスタンスのアドレスが等しいかどうかを表す。
同価性は、2つのインスタンスのアドレスにかかわらず、値が等しいかどうかを表す。
Objectのサブクラスは hashCodeと equalsをオーバライドすることができる。
どちらか一方をオーバライドした場合はもう一方もオーバライドする必要がある事と、equalsが成立する場合の hashCodeは等しくなければならない事は Javaと同じ。


lock, unlock, wait, notify, notifyAllは同期プリミティブである。これらを実現するために、pthreadの mutexと条件変数が使用している。
Cでは synchronizedな関数やブロックを記述することができないため、lockと unlockで代用する。分岐や大域脱出などにより unlock漏れが無いようにするのはプログラマの責任である。


wait, notify, notifyAllはいづれも lockと unlockでくくられていなければならない。
notify、notifyAllにより同一のモニタで waitしているスレッドの実行を再開させる。両者の違いは同一のモニタで waitしているスレッドが複数あるときに、notifyは waitしているもスレッドうちの1つだけを再開させるのに対し、notifyAllは全てを再開させることである。
notifyでどのスレッドが再開するかは特定することはできない。waitした順とは限らないし、いつも同じスレッドに偏るかも知れない。
notifyはスレッドプールのように要求を処理するスレッドを割り当てるために使用される。
notifyAllは複数のスレッドでタイミングを同期させるために使用される。



実質的増税が続くなか、関税は下がる傾向にある。 関税も上げよ。

2013年4月21日日曜日

libdwarfのインストール

softiesプロジェクトで使用するかは未だ決めてないが、実験のため Open Solarisに libdwarfをインストールしてみた。

ダウンロード (download)下载

David A's DWARF Pageから
libdwarf-20130207.tar.gzをダウンロードした。

ビルド (build) 构建

tarで展開し、dwarf-20130207にチェンジディレクトリして
sudo ./BLDLIBDWARF

すると自動的にビルドが始まるが、dwarfdumpをビルド中にコケてまう。

# gcc -E tag_tree.list does not work, so use a .c name
rm -f  tmp-t1.c
cp ./tag_tree.list tmp-t1.c
gcc  -g -O2 -I. -I. -I./../libdwarf -DCONFPREFIX=/usr/local/lib  -E tmp-t1.c
  > ./tmp-tag-tree-build1.tmp
./tag_tree_build -s  -i tmp-tag-tree-build1.tmp  -o tmp-tt-table.c
make: *** [tmp-tt-table.c] Segmentation Fault (core dumped)
make: *** Deleting file `tmp-tt-table.c'

欲しいのはライブラリとヘッダのみのため、libdwarfの中だけビルドする。


./configure, makeすれば良いのだが、configure時のオプションでビルドするライブラリのタイプを選択できる。

ライブラリタイプ.configureのオプション
libdwarf.aオプションなし
libdwarf.so--enable-shared --disable-nonshared
両方--enable-shared

インストール (install) 安装

make installは提供されてないので、ハンドコピーする。

ファイルインストール先ディレクトリ
libdwarf.h, dwarf.h/usr/local/include
libdwarf.a, libdwarf.so/usr/local/lib

libdwarf.hは展開したファイルには無く、configure時に libdwarf.h.inからコピーされる。

2013年4月11日木曜日

コーディング規約 その3 (code conventions 3)

コーディング規約の 3回目は、ソフトウェアメトリクスに関するものである。

我々は、2005年にはメトリクスの計測ツールを導入したが、その後、使用するツールの都合により一時的に自動的な計測を停止している。
それでも、以下に掲げる規約は有効である。

なお、これらの基準を逸脱しなければならない場合は、実験的なプロジェクトの場合を除いて社内レビューを行い、その妥当性の検証が必要となる。

関数の大きさ

1個の関数(メソッド)の大きさは、7行までが理想で、多くともこれに +2した 9行が良い。

ただし、言語によっては使用できる構文が違うため、多少差をつけることにしたい。
  • Java -- 12行
  • c++ -- 24行
  • c -- 30行
  • ほか -- T.B.D.
1980年代半ば、私が学生アルバイトをしていた会社の社長と、関数(アセンブリ言語だったのでサブルーチンと読んでいたが)を小さくすると各々は単純になるが、数が増えたり、レベルが深くなって判りにくくなることを議論した記憶がある。そのときには良い答えを見つけることはできなかった。

2000年代に入っても、プロジェクトのメンバにリファクタリングして関数を単純にしてもらえないか相談したときに、彼らから同じような答えが返ってきた。 彼もまた、解決法を見つけていないようだ。

大事なのは関数が大きくなればなるほど「関数を分けなければならないのではないか?」という疑問を持つことである。

上記の基準はちょっと厳しいと思われるかもしれないが、頭の中でトレースするのにはあまり長くないほうが良い。

ほかの考え方としては、できれば 1個の関数を読むために、左右はもちろん、上下にもスクロールしたくない。

引数の数

気軽に使ってよいのは 3個まで。
Immutableなオブジェクトをコンストラクタで全部初期化したい場合でも 7個まで。

ローカル変数の数

3個までに収めるのが理想。言語別上限はつぎの通り。
  • Java -- 5個
  • C++ -- 7個
  • C -- 7個

制御構文の深さ

if, switch-case, for, while, do, try-catchおよび、 ? : 演算子を合わせて 3レベル。

厳しいと思われるかもしれないが、これを守るとコードが読みやすくなる。

使われ方としては、
  • tryを使う場合には、中に一重のループとその中に if文が 1レベル。
  • 二次元データを扱うにはループを二重に使いたい場合があるので、そうするとあと if文が 1レベル。
  • switch-caseの中に if文があって、その中で ? : 。
なお、3レベル以内だったとしても switch-caseの中で switch-caseを使用するのは非推奨。(我々の規約では 8タブを使用するので良いが、 4タブだとどの caseがどの switchに対応するか追跡するのが面倒になり、間違いの元になる。

クラスの大きさ

メンバの変数や関数の数に制約は無いが、空行、コメントを合わせて理想は 500行まで。最大 1000行まで。

大事なことは、凝集度。

分けてはいけないものを分けると無駄な相互作用が発生し、そのためのアクセサが増えるだけでなく、そのアクセサが外部に公開されてしまうことが適切かどうかという問題が生ずる。

逆に一緒である必要のないものを1つにしてしまうと、ある部分の変更が外の部分に影響が及ばないか検証する範囲が広がり、無駄なコストが発生することである。

目覚めよ!正恩。

2013年4月9日火曜日

コーディング規約 その2 (code conventions 2)

前回に引き続き、弊社のコーディング規約を紹介する。

なお、命名規約はローカル変数やプライベートなメンバを除いて設計規約の一部として扱っているため、コーディング規約には現れない。

マクロ置換の禁止 (Prohibition of the macrosubstitution)

マクロはしばしば取り上げられるような引数評価の副作用が問題であるだけでなく、可読性を悪化させる原因にもなる。
本機約では、例外的に標準ライブラリを使用する上で必要となる最小限の使用を認めているが、それ以外の使用は原則的に禁止である。
softiesプロジェクトではこの原則を 2つ破っている。

一つは、importを実現するためのもの。

もう一つは、例外機構の try, catch, finallyである。

前者は置換のルールが単純で混乱を起こすことは少ないと思われるが、後者は危険が多い。そのため例外機構は完全な実験状態であり、ライブラリ中で使用している箇所はまだないのである。
外にも publicや privateなどの可視性を表すものなどがあるが、使用するかしないかは選択できるようになっている。


Javaではアノテーションが追加されるまではコンパイル制御機能はソースコード上に記述できなかったため、記号定数は全てクラスの定数として使用される。
問題は、 C/C++の場合である。
本規約では、記号定数は全てマクロではなく定数 (constant value)として定義しなければならない。
関数型マクロは、C++の場合テンプレートで置換することができる。
Cにはテンプレートが無いため、全て通常の関数を記述しなければならない。多態性実現のために関数型のマクロをしては成らない。
なお、C++のテンプレートもデバッグを難しくする恐れがあるため、慎重に定義しなければならない。それが難しい場合には C同様にいちいち通常の関数を記述することになる。


記号定数の置換のメリットは、単にリテラルをロジックから排除することによる可読性の向上だけでなく、使用されない定数が多くある場合には通常の定数を用いるよりもメモリ削減になる点である。
我々はそのメリットを犠牲にしても、混乱を避けることを選択した。

条件付コンパイルの禁止 (Prohibition of conditional compilation)

#if, #ifdefなどの条件付コンパイルはマクロ置換よりもさらに可読性を悪化させる。
そのため本規約では、多重インクルード防止目的のもの意外の使用は禁止である。

製品コードの中に #if 0 などが含まれるのは論外で、仮にデバッグ用に使用されたとしてもコミット時までには除去されなければならない。
コードが複数の機種や仕向けの仕様を共通で使用するためにも #ifが仕様されることがあるが、弊社では禁止である。
バージョンの切り替えも禁止である。
これらは、デザインパターンで回避するか、適切な構成管理を行うことで解決が可能である。
なお、回避、解決法は何通りかのパターンがあるため、ここでは触れないが、機会があれば紹介したい。

goto文の使用 (using goto statement)

多くの場合、可読性を低下させる。
エラーハンドリングでは gotoを使ったほうがコードがすっきりするという考え方もある。
実際、μITRONのある実装では gotoを使った場合の効果的なエラーハンドリングのコードを見たことがあるが、誰がやってもうまくいくとは限らない。特に関数の中の構造が複雑だとうまくいかない。
弊社の設立前、1990年代中ごろに筆者が携わったものの中にすさまじいコードがあった。それは国内のある会社が実装した CADDAM用のカスタムコード(プラグインソフト)が正常に動作しないため改修を請け負ったのだが、1個の switch caseが 6千行あり、そのほとんどの caseが breakではなく gotoで終わっているような代物だったのである。
goto文を breakに置き換えてみるとなんと、相対ジャンプの距離が遠すぎてコンパイルエラーになるではないか。
つまり、元の作者はそのエラーを逃れるために gotoを用いたようだ。
このような使い方をしなければならない状況と言うのは、何か怪しいのである。まず、保守不可能と言ってよいだろう。


話が遠回りしてしまったが、本規約では goto文の使用は C++では禁止。Cでは禁止しないが、限りなく非推奨に近い。


例外処理機構を有効に用いる手段ができれば、Cでも禁止することになるだろう。

do while文

do while文の使用は禁止していないが、for文、while文を優先的に使用することが推奨される。

定数との比較

定数との比較時に、誤って代入してしまうのを防止するために、しばしば比較する定数を左辺に記述する規約が存在する。
例) WRONG
    if (0 == value)
        ...

本規約では、このような書き方は禁止であり、つぎのように記述しなければならない。
例) RIGHT
    if (value == 0)
        ...

これは、valueが 0と等しいかどうかを判定したいのであり、0が valueと等しいかを判定したいのではないからである。
2000年頃に私が隣のプロジェクトのメンバとした議論の中では、前の書き方でも『0 と valueが等しいかと読めばよいだろう。』という意見がったが、比較するものの主語がどちらかを考えると 0 == valueでは不適切である。


我々の記述方法では誤って値を代入してしまうではないかと思われるかもしれないが、徹底した単体テストで補うことで解決している。
なお、Javaでは、 if (value = 0)と書いてしまってもコンパイルエラーになるため、バグになることは無い。

ブール値との比較禁止

ブール値の判定に比較演算子を用いてはならない。
たとえば、次の例は禁止である。
例) WRONG
    if (files.hasNext() == true)
        ....

この例では、==と trueの組み合わせであるからまだマシなほうで、これが !=や falseとの組み合わせであったら読み手が理解しにくい。

つぎの例のように、定数との比較を行わないことが正しい。
例) RIGHT
    if (files.hasNext())
        ...

hasNextは「次を持っている」と読めるので、何も trueと比較して等しいことを確認する必要は無いのである。
逆に言えば、関数名を付ける場合には hasXXXや isXXX、containsのように「読める」名前を使用しなければならない。

悪い関数名の代表は check()。 チェックしてどうなったら trueなのか、ドキュメントを参照しなければ理解できないだろう。

我々が使用する関数名で validate()というのがあるが、これはブール値を返すのではなく受理されない場合には例外を返すため問題ではない。

関数の途中からの脱出

原則として関数の途中脱出は禁止である。

これには幾つかの例外がある。つぎの場合の途中脱出は認められる。
  • 関数の冒頭で引数や状態異常を検出したことによる return文によるリジェクト
  • 例外がスローされる場合
このほかに exit()もあるが、正常終了であれ異常終了であれ、可能な限り関数の最後に exit()を記述しなければならない。


鳥インフルエンザが流行りそうだな。万一のときはドクターに頼んでコルドラジンをほんの数滴打ってもらうことにしよう。
Bird flu seems to be popular. Let's decide to have I ask a doctor at the time of emergency and inject just several drops of Cordrazine.

2013年4月7日日曜日

コーディング規約 その1 (code conventions 1)

弊社にはプログラミングを行う際のコーディング規約があり、Sofitiesを含めた自社プロジェクトは勿論、請負業務などで先方から指定された規約が無い限り常にこれが適用されている。

業界で一般的に推奨されているものとは異なるものや時代錯誤的なものがあり、また多少流動的でもあるが、全く合理性を欠くものではない。
幾つか紹介してみよう。

字下げは、タブ文字を使用(use tab character for indentation)

XMLには空白文字(半角スペース) 4字以下の字下げを使用することがあるが、そのほかの言語ではほぼ例外なくタブを使用する。
Digital Reserch社の CP/M 2.2時代の名残である。
当時は主記憶、外部記憶の容量が小さかったため空白文字を沢山並べるのは不経済であった。
タブ文字を使用すると多くの場合ファイルサイズが半分以下になる。
欠点としては、タブ文字と空白文字を混在させてしまうと、醜くみにくくなること。

8タブを使用(8 characters tab stop)

弊社の規約の多くが Kernighan and Ritchieの "THE C PROGRAMMING LANGUAGE"に由来しているが、これは例外の一つ。
同書では原点、共立出版による邦訳版ともに 5文字タブを使用している。
多くのテキストエディタが 4または 8文字がデフォルトであることを考えると、5文字は半端である。
4タブを使用する会社が多い中、8タブを使用する理由はつぎの通り。
8タブを使用すると、ネストを深くしたときに内容がどんどん右にいってしまう。すると4タブを使用したい誘惑に駆られるが、そうしてしまうと 1個の関数、サブルーチンが複雑になってしまう。
8タブを守らせることによって、ネストが深くなりそうなときに関数を分割する習慣が付くようになる。

1行は 80文字以下(line length limit at 80 characters)

文字端末は 80文字までしか表示できないものが多かった。
ビットマップが主流となった現在ではハード的な理由は無くなった。しかし、画面が広くなったからと言って際限なく長い行はいただけない。
目で追うときに画面の右端まで行って、つぎの行の先頭に戻るときに見失ってしまう。 況してや左右方向にスクロールさせるなどは持っての外である。
画面を左右に並べてマージ作業するときなどを考えると 80文字くらいが丁度良い。

空白のとり方(spacing)

つぎのいづれかの文字の後には原則として空白類が必要、前には置かない。 ',', '.', ';', ':', '!', '?', ')', '}', ']'
例)
    a[i] = 0;           RIGHT
    a [ i ]= 0;         WRONG


単項演算子のうち次のものの後には空白類は置かない。 '*'(C, C++), '&'(C, C++), '-', '!', '~', キャスト, 前置の '++', '--'
例)
    void *p;            RIGHT
    void * p;           WRONG

    --index             RIGHT
    -- index            WRONG

    (int)longValue      RIGHT
    (int) longValue     WRONG


後置の "++", "--"の前後には空白類は置かない。
例)
    pointer++;          RIGHT
    pointer ++ ;        WRONG


乗法演算子、加法演算子、シフト演算子、関係演算子、等値演算子、ビットごとの演算子、論理的演算子、条件演算子、代入演算子の前後には空白類が必要。
例)
    a = b + c;          RIGHT
    a=b+c;              WRONG


次の演算子の前後には空白文字は置かない。 '.', "->"(C, C++), "::"(C++) ただし、行を折り返す場合は可。
例)
    record->field1 = 0;         RIGHT
    record -> field1 = 0;       WRONG


関数の宣言、定義、呼び出し時の小括弧の前には空白は置かない。
例)
    printf("Hello.\n");         RIGHT
    printf ("Hello.\n");        WRONG


関数の宣言、定義、呼び出しの小括弧の内側には引数、パラメタをコマで区切る箇所を除いて空白は置かない。
例)
    Math.max(a, b)      RIGHT
    Math.max( a, b )    WRONG


関数呼び出しの跡の乗法演算子、加法演算子、シフト演算子、関係演算子、等値演算子、ビットごとの演算子、論理的演算子、条件演算子、代入演算子との間には空白類が必要。それ以外には置かない。
例)
    fabs(v) < 1.0               RIGHT
    fabs(v)< 1.0                WRONG

    fp = fopen("text", "r");    RIGHT
    fp = fopen("text", "r") ;   WRONG
キーワードと小括弧の間には空白文字が必要。ただし、sizeof演算子の場合は置かない。
例)
    if (true)           RIGHT
    if(true)            WRONG

    sizeof(int)         RIGHT
    sizeof (int)        WRONG
sizeof演算子は括弧つきで使用する。
例)
    sizeof(int)         RIGHT
    sizeof int          WRONG
ブロックの中括弧の前には空白が必要。
例)
    if (true) {         RIGHT
    if (true){          WRONG

中括弧の取り扱い (braces)

条件分岐や繰り返しの制御構文の中が単一の文の場合はブロックを使用しない。
例)
    if (end)            RIGHT
            exit(0);

    if (end) {          WRONG
            exit(0);
    }
業界では括弧の対応が不一致になるのを恐れて {を強制する規約が多いと思う。 しかしそれは記述が複雑だったり、ネストが深くなるからであり、シンプルに書くことを心がければ、反って不要な括弧などは付けないほうが良い。 行の折り返しが必要な場合を除いて、{の前で改行しない。
例)
    if (true) {         RIGHT

    if (ture)           WRONG
    {

    int atoi(int i) {   RIGHT

    int atoi(int i)     WRONG
    {
ブロックを使用しない制御構文であっても、条件式の行と制御される行の間は改行する。
例
    while (!end)                RIGHT
            call();

    while (!end) call(); WRONG
} と elseの間は改行しない。
例)
    if (ready) {        RIGHT
      ...
    } else {

    if (ready) {        WRONG
      ...
    }
    else {

文字コードは UTF-8 (using UTF-8)

2000年ころまでは EUC-JPを使用していたが、中国語などの日本語以外の文字を混在させようとしたときを考慮して、UTF-8に切り替えられた。
お尻かゆい虫~

2013年4月2日火曜日

パッケージ一覧

昨年末、本社から徒歩移動可能なところに開発室用のオフィスを借り、1月には主要な開発機材を移転した。

2~3月には大小併せて 340個近いプロジェクトのファイルのマージに追われた。
法人化前から作り散らかし、ディレクトリ単位でフォークが発生していたもので気の遠くなる作業だった。
ずいぶんと不精したものだ。
8"FDや QICの接続が未完の為、1998年以前のものは未だマージできていない。


分類プロジェクト数
J2ME4
J2ME/PBP3
J2ME/XLET2
J2SE1
C17
LWT57
NetBeans3
Swing51
実験段階160
自社用途6
そのほか33

LWTには組み込み Java用のサービスやライブラリ、フレームワークで、既に出荷済みの RDBや サーブレットコンテナ、状態遷移マシンなどが含まれている。

softiesプロジェクト パッケージ一覧

softiesプロジェクトもフォークしていたものが見つかったので、マージが行われた。
下記に現状のテストサマリを示す。 パスしてないテストも多い。 カバレッジレポートは示してないが、これも低い。

coreパッケージ


langパッケージ


ioパッケージ


netパッケージ


utilパッケージ


javaパッケージ


x11パッケージ



アイちゃんが逝く! そういえば長いこと日糧パン食べてないな。敷島か山崎しかないし。