2012年7月28日土曜日

構造体(structure)と typedef

C++の classは(可視性のデフォルトを除けば)structと同じである。 C++の structは言語 Cのそれを拡張したものに過ぎない。実際、James Rumbaughの OMT第16章 非オブジェクト指向言語では Cの structを用いてクラスを実現する方法が紹介されている。

ここでは classの実現方法はひとまず置いといて、softies projectに於ける structの使用方法について考察してみる。

Kernighan und Ritchieより

プログラミング言語 Cの 6.9章では、構造体を typedefする例としてつぎのものが示されている。
typedef struct tnode {    /* the basic node */
    char *word;           /* points to the text */
    int count;            /* number of occurrences */
    struct tnode *left    /* left child */
    struct tnode *right;  /* right child */
} TREENODE, *TREEPTR;
このように、typdefと同時に structを定義する場合について考える。

もう少し簡単例を示す。
typedef struct Type {
    Type* child;
} Type;

typedefと struct定義を 1つの分で記述するとすっきりするのだが、自己参照(あるいは循環参照)する場面では、定義が完結していないため内部で typedefした結果を使用することができない。typedefと structを 2つの文で定義することで解決される。
typedef struct Type Type;
struct Type {
    Type* child;
};

さすがに最近はこのようなコードを示しても不思議がられる事は無いが、90年代にはしばしば「ビルドが通るか?」と疑問が投げかけられ、そのたびに説明が必要だった。

疑問の1つ目は、structの名前と typedefする名前がどちらも Typeで良いのか?というものであった。タグをつけるときには Type_tのようにするが、ここでもそうしなければならないという主張であった。 言語 Cでは両者が一緒の名前でも問題はない。 struct Typeと Typeを使い分けなければならない場面は明確で紛らわしくはならないし、別々の名前を考えるほうが手間が生じるため、両者は一緒にしておいたほうが楽である。

2つ目は、struct定義よりも先に typedefできるのか?というものである。 K&Rの 6.9では typedefが「宣言」と書かれていることから判るように、定義は不要である。

そして3つ目は、循環参照する設計は悪い!というものである。 しかし、オブジェクトを分析、設計すると循環するのが自然なものに遭遇する。 GoFが浸透してからは Composite Patternと言えば納得してもらえるようになった。

ポインタも含めてしまおう

オブジェクトを mallocにより heapから割り当てることが多いため、いちいち Type*のようにポインタであることを明示しなくとも、Typeで済ませたい。
typdef struct Type* Type;    /* pointer included */

struct Typeと書かなければならないのはそれを定義するときと sizeof演算子で構造体の(ポインタではなく本体の)サイズを求めるときくらいだから、このように、* も typedefの中に含めてしまえばすっきりする。

C++との互換

softies projectは言語 Cのみを対象としていて、C++との相互利用は原則として考慮されていない。しかし、softiesのテストフレームワーク CUnit packageは CppUnitよりも便利かも(手前味噌)と感じたので、自動車業界での組み込み系 C++プロジェクト内で実際に使ってみた。すると当然ながら Cと C++の違いによる問題が幾つか表面化したのだが、typedefでは上述の typedef struct Type* Type;のように構造体名と typedefの名前を一緒にできないことが判明した。 C++からもアクセスする用途では、ちょっと癪に障るが、
typedef struct Type_t* Type;   /* when the use is required from C++ */
のように両者の名前を変えるのがよい。

幸い、CUnit packageではこのようにしなくとも回避することができたのと、softies projectの CUnit以外の成果を C++から使用する意義がない(オブジェクト指向言語であるC++はその真似事をする必要がないのと、専用のライブラリが充実している)ため、暫くはこの回避策は必要なさそうだ。

このままで挿れたら♥ って思う瞬間まで

2012年7月26日木曜日

命名規則 (naming convention)

softies projectの使用言語は Cであるが、命名規則は Kernighan und Ritchieの伝統を基礎に、オブジェクト指向に対応するためのいくつかの制約と拡張を試みている。


基本原則

K&Rに掲載されている典型的な変数名、関数名を次に示す。
  1. line
  2. getword
  3. day_of_year
  4. op2
  5. _iob
これらは英小文字と下線'_'、2文字目以降には数字を使用する。1つの名前が 2つ以上の単語で構成される場合は空白で区切ることができないため、単語を続けて記述する方法と下線'_'で繋ぐ方法があるが、後者の方が比較的読みやすい。しかし、我々は '_'をパッケージ名やクラス名の区切りとして使用するために通常の単語の区切りとしては使用しないことにした。代わって各単語の先頭を大文字にする CamelCase方式を採用することにした。これは X Window Systemなどで使用されてる。

オブジェクト指向の構成要素を区別しやすくするためにパッケージ名は英小文字と数字のみ、クラス名は英大文字で始める UpperCamelCase、関数や変数名は英小文字で始める LowerCamelCase、そして定数は英大文字と数字そして下線で記述する。


クラス名(class name)

たとえば、Threadクラスのスタックトレース出力を行う関数を表現する場合は次のようになる。
Thread_dumpStack()

_の前は Threadがクラス名(class name)、_の後は dumpStackが関数名(function name)、オブジェクト指向的にいえば、操作名(operation name)あるいはメソッド名(method name)である。

さらに、この Threadクラスが lwd:langパッケージに属する場合は次のようになる。
lwd_lang_Thread_dumpStack()

このように変数名や関数名に接頭辞のごとくクラス名をつけることには訳がある。プログラムの中で名前の衝突を回避しなければならないことがあり、大規模になるほど名称の管理は難しくなる。クラス名や後に述べるパッケージ名をつけることにより、衝突の回避がしやすくなる。たとえば、Streamクラスと Textクラスに各々 read関数を定義されていても良い。

パッケージ名(package name)

パッケージ名(package name)は 1つ以上の名前空間(name space)で構成されている。各々の名前空間は前述のとおり英小文字か数字で表現するため、名前空間は '_'で区切ることができる。

悪い例(bad example)
lwd_Lang_Thead_Dump_Stack

パッケージ名、クラス名、関数名等に各々大文字を使用したり、単語の区切りに '_'を使用してしまうと、どこまでがパッケージ名、クラス名、関数名なのか区別できなくなる。
同様の理由で、名前空間の名称の単語を '_'で区切ったり、変数名や関数名を '_'で始めたり、終えたりすることはしない。


定数(constant)

定数は、#defineて定義される記号名に加え、メモリに配置される const値も、単語を '_'で繋いだ英大文字および数字で表記する。 つぎに示す例は、前述の Threadクラスで定義される最大優先順位を示す名称。
lwd_lang_Thread_MAX_PRIORITY


局所名(private name, local name)

ソースモジュール内でのみ有効な privateな変数、関数の名称はグローバルではないため名称の衝突を心配する必要はない。また、関数内でのみ使用されるローカル変数の名前も同様である。これらはパッケージ名やクラス名を省略してできるだけ単純な名前をつけるのが良い。

例)
dumpStack


補足

これまで説明したとおり、パッケージ名まで表現しようとすると長い名前になる。しかし、これは冗長というわけではない。

K&Rで推奨している 8文字には収まりそうも無い。しかし近代的な処理系であっても名称の長さの上限がなくなったわけではない。255文字に収まるのが適当と思われる。


四川は、まるでレーザービーム

2012年7月15日日曜日

charのサイズ その2

charのサイズのづつき

charを2バイトにするのは実験。普通なら wchar_tを使うべきでしょう。
早速何が起きているか確認。
$ cat literalTest.c
/* 15.Jul.2012 kei */

#include <stdio.h>
#include <stdlib.h>
#include "redefines.h"

int main() {
 char a = 'あ';
 printf("%x\n", a);
 return EXIT_SUCCESS;
}
$ cc -o literalTest literalTest.c
literalTest.c: In function ‘main’:
literalTest.c:8:11: warning: multi-character character constant [-Wmultichar]
literalTest.c:8:2: warning: large integer implicitly truncated to unsigned type
 [-Woverflow]
$ ./literalTest
8182
$ 

実行はできた。でも
$ echo あ | od -tx1
0000000 e3 81 82 0a
0000004
$ 

だから、e3が掛けているのが判る。
UTF-8は Unicodeの交換用外部フォーマットなので 3バイト。ならば、内部表現の UCS2を使ったらどうか。
$ cc -fexec-charset=ucs2 -o literalTest literalTest.c
literalTest.c: In function ‘main’:
literalTest.c:8:11: warning: multi-character character constant [-Wmultichar]
literalTest.c:8:2: warning: large integer implicitly truncated to unsigned type
 [-Woverflow]
literalTest.c:14:0: internal compiler error: character 0xa is not unibyte in
 execution character set
Please submit a full bug report,
with preprocessed source if appropriate.
See <http://gcc.gnu.org/bugs.html> for instructions.
$ 

おお今度は警告だけでは済まなかった。 gccの組み込み printfがエラーになったのかも。通常のライブラリ関数にしてみる。
$ cc -fno-builtin-printf -fexec-charset=ucs2 -o literalTest literalTest.c
literalTest.c: In function ‘main’:
literalTest.c:8:11: warning: multi-character character constant [-Wmultichar]
literalTest.c:8:2: warning: large integer implicitly truncated to unsigned type
 [-Woverflow]
$ ./literalTest
��$ 

今度は実行までできたが、出力は文字化け。 UTF-8な環境で UCS2を吐き出したのだから当たり前か。
とりあえず確認用に 16進ダンプを書いてみる。
$ cat literalTest.c
/* 15.Jul.2012 kei */

#include <stdio.h>
#include <stdlib.h>
#include "redefines.h"

void lineFeed() {
 byte b = (byte)0xa;
 write(fileno(stdout), &b, 1);
}

void dumpDigit(int d) {
 byte b = (byte)((d < 10) ? (d + 0x30) : (d + 0x61));
 write(fileno(stdout), &b, 1);
}

void dumpByte(int c) {
 int d1 = (c >> 4) & 0xf;
 int d2 = c & 0xf;

 dumpDigit(d1);
 dumpDigit(d2);
}

void dump(char c) {
 int c1 = c >> 8;
 int c2 = c & 0xff;

 dumpByte(c1);
 dumpByte(c2);
 lineFeed();
}

int main() {
 char a = 'あ';

 dump(a);

 return EXIT_SUCCESS;
}
$ cc -fno-builtin-printf -fexec-charset=ucs2 -o literalTest literalTest.c
literalTest.c: In function ‘main’:
literalTest.c:35:11: warning: multi-character character constant [-Wmultichar]
literalTest.c:35:2: warning: large integer implicitly truncated to unsigned type
 [-Woverflow]
$ ./literalTest
4230
$ 

'あ'は UCS2で 0x3042なので、データは大丈夫ようだ。

実例は示さないが、UCS2を使った場合いくつか注意点がある。
ASCIIの範囲でも上位バイトに 0x00が入るため、それなりのprintfでないとすぐに文字列が終端してしまい、なにも表示できない。
wprintfと Lつきのリテラルで英数字を表示することはできるが、漢字などはうまく表示できない。

UCS2な環境にするか、UCS2を扱える printfにするか、printf呼ぶ前に UTF-8に戻す必要があるだろう。

いづれにしても、通常用途ではお勧めできない使い方。

半年で気がついたらぁ

2012年7月12日木曜日

charのサイズ

softiesプロジェクトのために、char を2バイトにしてみる。


$ gcc --version
gcc (GCC) 4.6.0
Copyright (C) 2011 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

$ cat redefinesTest.c
/* 12.Jul.2012 kei */

#include <stdio.h>
#include <stdlib.h>
#include <redefines.h>

int main() {
 printf("sizeof byte: %d \n", sizeof(byte));
 printf("sizeof char: %d \n", sizeof(char));

 byte b = 0x2222;
 char c = 0x3034;
 printf("b: %x \n", b);
 printf("c: %x \n", c);

 return EXIT_SUCCESS;
}
$ cc -o redefinesTest -I. redefinesTest.c
redefinesTest.c: In function ‘main’:
redefinesTest.c:13:2: warning: overflow in implicit constant conversion [-Woverflow]
$ ./redefinesTest
sizeof byte: 1 
sizeof char: 2 
b: 22 
c: 3034 
$  

よしよし、ちゃんと警告も出ている。でも include順をまちがうと、大変なことになりそうだな。

多くの文字が 3バイトである UTF-8だと、 c = 'あ'; のようなリテラルの使用はうまくいかない。

愛の照明は?