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を用いた排他処理などが組み込まれていて、シグナルハンドラではそれがうまく機能ささせられないのである。
ライブラリ関数はほとんど使用できないが、システムコールはファイルアクセスであれ、ソケット通信であれ使用できる。



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