ラベル Cリファレンスマニュアル の投稿を表示しています。 すべての投稿を表示
ラベル Cリファレンスマニュアル の投稿を表示しています。 すべての投稿を表示

2009年2月23日月曜日

C言語におけるUTF-8の取り扱い確認


/*

ちょっと調査。

まずemacsでひらがなの「が」をファイルに書くとLinux
とOSXでは違いがあるのかどうか。そのファイルをodで比
べてみる。

まずOSX。

----
$ od -t x1 -t c HIRAGANA-LETTER-GA.txt
0000000 61 62 63 64 0a e3 81 8c 0a
a b c d \n が ** ** \n
0000011
----

続いてLinux。

----
$ od -t x1 -t c HIRAGANA-LETTER-GA.txt
0000000 61 62 63 64 0a e3 81 8c 0a
a b c d \n 343 201 214 \n
0000011
----

違いは無い。

さて、e3 81 8c をUTF-8にて解釈してみる。

ビットにすると、

----
CL-USER(13): (dolist (n (list #xe3 #x81 #x8c))
(format t "~b " n))
11100011 10000001 10001100
NIL
----

である。このビットパターンは、UTF-8としては

1110yyyy 10yxxxxx 10xxxxxx

であり、最大16bitのコードポイントを表現している。

具体的にコードポイント対応部分を抽出すると、

0011 000001 001100

さらにこれを右詰めoctetになおすと、

00110000 01001100

----
CL-USER(14): (dolist (n (list #b00110000 #b01001100))
(format t "~x " n))
30 4c
NIL
----

というわけで、これはNFCの「が」(U+304C)だ。

とどのつまりファイルの中身にはOSは関知していないか
ら、emacsやodが文字をUTF-8のNFCで取り扱っており、そ
こに一貫性がある、ということだろう。

続いてgccのプリプロセッサまでの処理における文字の取
り扱いを確認したい。

前章で実験したところ、gccはC99に完全準拠していなかっ
た。ソースファイルの識別子に多バイト文字が使われて
いた場合は、それをプリプロセス前に国際文字名
(\uxxxx)に変換しなければいけないのだが、それをやっ
てくれていないようだ。それを確認する。

*/
----
#include <stdio.h>
int main(void)
{
int が;
for (が=1;が<10;が++)
printf("%d ", が);

return (0);
}
----
/*

こんなソースでためす。これのod。

----
$ od -t x1 -t c i18n-character-name-2.c
0000000 23 69 6e 63 6c 75 64 65 20 3c 73 74 64 69 6f 2e
# i n c l u d e < s t d i o .
0000020 68 3e 0a 69 6e 74 20 6d 61 69 6e 28 76 6f 69 64
h > \n i n t m a i n ( v o i d
0000040 29 0a 7b 0a 20 20 20 20 20 69 6e 74 20 e3 81 8c
) \n { \n i n t が ** **
0000060 3b 0a 20 20 20 20 20 66 6f 72 20 28 e3 81 8c 3d
; \n f o r ( が ** ** =
0000100 31 3b e3 81 8c 3c 31 30 3b e3 81 8c 2b 2b 29 20
1 ; が ** ** < 1 0 ; が ** ** + + )
0000120 0a 20 20 20 20 20 20 20 20 20 20 70 72 69 6e 74
\n p r i n t
0000140 66 28 22 25 64 20 22 2c 20 e3 81 8c 29 3b 0a 0a
f ( " % d " , が ** ** ) ; \n \n
0000160 20 20 20 20 20 72 65 74 75 72 6e 20 28 30 29 3b
r e t u r n ( 0 ) ;
0000200 0a 7d 0a
\n } \n
0000203
$
----

これをgcc -Eで処理。

includeしているから長くなるが、とにかく変換はしていないようだ。

*/
----
# 1 "i18n-character-name-2.c"
# 1 "<built-in>"
# 1 "<command line>"
# 1 "i18n-character-name-2.c"
# 1 "/usr/include/stdio.h" 1 3 4
# 64 "/usr/include/stdio.h" 3 4
# 1 "/usr/include/_types.h" 1 3 4
# 27 "/usr/include/_types.h" 3 4
# 1 "/usr/include/sys/_types.h" 1 3 4
# 32 "/usr/include/sys/_types.h" 3 4
# 1 "/usr/include/sys/cdefs.h" 1 3 4
# 33 "/usr/include/sys/_types.h" 2 3 4
# 1 "/usr/include/machine/_types.h" 1 3 4
# 34 "/usr/include/machine/_types.h" 3 4
# 1 "/usr/include/i386/_types.h" 1 3 4
# 37 "/usr/include/i386/_types.h" 3 4
typedef signed char __int8_t;


... 中略 ...


static __inline int __sputc(int _c, FILE *_p) {
if (--_p->_w >= 0 || (_p->_w >= _p->_lbfsize && (char)_c != '\n'))
return (*_p->_p++ = _c);
else
return (__swbuf(_c, _p));
}
# 2 "i18n-character-name-2.c" 2
int main(void)
{
int が;
for (が=1;が<10;が++)
printf("%d ", が);

return (0);
}
---
/*

では、文字列定数の中だとちゃんとやってくれるのか?
というところで同じソースを変更して確認する

*/
----
#include <stdio.h>
int main(void)
{
int i;
for (i=1;i<10;i++)
printf("%d が", i);

return (0);
}
----
*/
/*

あり、だめだ。次のように、変換していない。

*/
----
前略 ...

static __inline int __sputc(int _c, FILE *_p) {
if (--_p->_w >= 0 || (_p->_w >= _p->_lbfsize && (char)_c != '\n'))
return (*_p->_p++ = _c);
else
return (__swbuf(_c, _p));
}
# 2 "i18n-character-name-3.c" 2
int main(void)
{
int i;
for (i=1;i<10;i++)
printf("%d が", i);

return (0);
}
----
/*

というわけで、gccの字句関係処理はC99の要求まんまに
実装されているのではなさそうだ。ソース文字集合が
UTF-8そのものである、という作りに見受けられる。

さて、次にファイル名の取り扱いを確認したい。ファイ
ルの中身とちがって、ここはOSの関与がある部分だ。
"が.txt"というファイルを作成する。これの中身を表示
するプログラムで振舞いを確認してみる。APIはCの標準
ライブラリを使う。これは、予測としては不一致なくう
まくいくはず。gccの標準ライブラリがfopenの引数たる
ファイル名をOSXならOSX向けに取り扱ってくれるという
予測だ。

----が.txt---
abcd

-------------

*/
----ga-opne.c----
#include <stdio.h>

int main(void)
{
FILE *fp;
fp = fopen("が.txt", "r");

if (fp == NULL)
printf("failed.\n");
else {
printf("Succeed.\n");
fclose(fp);
}
return (0);
}
--------
/*

----
$ gcc -std=c99 ga-open.c
$ ./a.out
Succeed.
$
----

うまくいった。

さて、ファイル名の取得はどうだろう。ディレクトリに
含まれるファイルの名前を取得する方法は標準Cにはない。
よって、ここから先は処理系次第となる。ただし処理系/環境
がPOSIXだとかSUSとかに対応しているなら、それらAPIを
使うということである程度のポータビリティは期待でき
る。

さて、この領域は、Advanced Programming in the UNIX
Environment (apue.2e)だろう、ということでapue.2eか
らサンプルをとってくる。

----ls1.c----
*/
#include "apue.h"
#include <dirent.h>

int
main(int argc, char *argv[])
{
DIR *dp;
struct dirent *dirp;

if (argc != 2)
err_quit("usage: ls directory_name");

if ((dp = opendir(argv[1])) == NULL)
err_sys("can't open %s", argv[1]);
while ((dirp = readdir(dp)) != NULL)
printf("%s\n", dirp->d_name);

closedir(dp);
exit(0);
}
/*
--------

apue.2eのサイトにあがっているソースをLeopard上でコ
ンパイルするには多少調整が必要。apue.2eが発刊された
ときは、10.3.xだったのだ。それから10.4、10.5とかわ
るなかでOSXはPOSIX対応を進めたため、ヘッダ関係で混
乱があるようだ。

いくつか調整作業をしてmakeと次のコマンドでls1.cをコ
ンパイルした。

gcc -ansi -I/Users/aka/scratch/c-ref/apue.2e/include -Wall -g -DMACOS -L../lib ls1.c ../lib/libapue.a -o ls1

で、そのls1で、が.txtを含むga-testディレクトリの中
身をOSから取得する。



(gdb) n
..
(gdb) p dirp->d_name
$10 = "..\000\000?\r\000\024\000\b\nが.txt\000*", '\0' <repeats 231 times>
(gdb) n
(gdb) p dirp->d_name
$11 = "が.txt\000*", '\0' <repeats 243 times>
(gdb) p/x dirp->d_name
$12 = {0xe3, 0x81, 0x8b, 0xe3, 0x82, 0x99, 0x2e, 0x74, 0x78, 0x74, 0x0, 0x2a, 0x0 <repeats 244 times>}
(gdb)

で、ここでメモリに入ってる

0xe3, 0x81, 0x8b, 0xe3, 0x82, 0x99

が、"が"なのだが、これを先程と同じ方法でUTF-8として
読み解くと、

U+304b U+3099
[HIRAGANA LETTER KA] [COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK]

であることがわかる。ここでNFDがあらわれた!

そのあとは、printfで%sしてるだけなので、この
UTF-8(NFD)が正しく表示されるかは、その表示を担当す
るものによりけりになる。上記のls1->gdb->emacs->screen->Terminal
では実は正常に表示されず、が.txtの"が"と"."の間に妙な文字が表示
されてしまう。(コピペした際に上のgdb出力のこの異常は消えちゃった)
Terminal直ならば問題なくて、

------------
$ ./ls1 ga-test
.
..
が.txt
$
------------

となる。

さて、ここまで来たところで、apue.2eのreaddirの出自
を確認する必要がある。

apue.2eによると、POSIX.1とのこと。
とするとmanにあるのか? man readdir、あった。

-----------
DIRECTORY(3) BSD Library Functions Manual DIRECTORY(3)

NAME
closedir, dirfd, opendir, readdir, readdir_r, rewinddir, seekdir, telldir --
directory operations

LIBRARY
Standard C Library (libc, -lc)

SYNOPSIS
#include <dirent.h>

...
-----------

するとdirentもmanにあるのか? あった。

-----------
DIR(5) BSD File Formats Manual DIR(5)

NAME
dir, dirent -- directory file format

SYNOPSIS
#include <sys/types.h>
#include <sys/dir.h>

DESCRIPTION
Directories provide a convenient hierarchical method of grouping files while
obscuring the underlying details of the storage medium. A directory file is
differentiated from a plain file by a flag in its inode(5) entry. It consists
of records (directory entries) each of which contains information about a file
...
-----------

で、

-----------
*/
struct dirent {
ino_t d_ino; /* file number of entry */
u_int64_t d_seekoff; /* length of this record */
u_int16_t d_reclen; /* length of this record */
u_int16_t d_namlen; /* length of string in d_name */
u_int8_t d_type; /* file type, see below */
char d_name[MAXPATHLEN]; /* name must be no longer than this */
};
/*
-----------

なそうであり、d_nameの中身がどうあるべきかは無い。
まあ、ファイルシステムは環境によって違うからなぁ。

というわけで、このPOSIX.1のreaddir経由でOXとアプリ
がファイル名をやりとりする場合は、エンコードについ
てはアプリ側がOSが何を選択しているかをどこかで知っ
た上で使わないといけないということだ。
*/

これで、Subversionにおけるファイル名の取り扱いを探る準備ができたのかなぁ。

こつこつ。

2009年2月22日日曜日

【C:ARM5】2 字句要素

うーん。字句について理解するだけで結構時間がかかった。。。
めげない、めげない。

/*

2 字句要素 (Lexical Elements)

- "This chapter describes the lexical structure
of the C language--that is, the characters that
may appear in a C source file and how they are
collected into lexical units, or tokens."


2.1 文字集合 (Character Set)

- ソース文字集合(source character set)というとき、
それはいわゆる文字集合そのままであり、エスケープ
表記等は含んでいないことに注意。

- ここでは文字コードは指定していない。文字集合の指
定である。

- 基本的にはISO/IEC 10646のBasic Latinブロックだよ。

- 国によってはその国の文字集合にBasic Latinの中の
文字が含まれていないこともある。そこでCには
ISO/IEC 646-1083のInvariant Code Setだけで書ける
ように、漏れた記号をInvariant code setの文字で表
現する仕組みがあるよ。

2.1.1 実行時文字集合 (Execution Character Set)

- クロスコンパイルを考えよ。ソースをコンパイルする
ときの環境の文字集合とプログラムが実行される環境
の文字集合は必ずしも同じではない。

- 実行時文字集合(execution character set)はバック
スラッシュによるエスケープ表記も含む。

- ソースをコンパイルするときの環境と実行するときの
環境が同じなら、ソース文字集合と実行時文字集合は
同じになる。

2.1.2 空白類文字と行の終わり (Whitespace and Line Termination)

- 空白類文字は隣り合う字句を分離する働きを持つ。

- 論理ソース行の例。gccでコンパイルできた。
*/

int main(void)
{
if (1==2) 1; el\
se 2;
return (0);
}

/*

2.1.3 文字コード (Character Encoding)

- (実行時)文字集合に含まれる各文字には何らかの慣用
的な数値表現が割り当てられている。これを文字コー
ドと呼ぶ。

- Cでは文字コードについていくつかの制約は課すが、
細かな指定はしない。

2.1.4 3文字表記 (Trigraphs)

- ISO 646-1083 Invariant Code Set に含まれる文字だ
けでCプログラムを書くための仕組み。

- 3文字表記(trigraphs)の処理は、字句解析の前に実施
される。かなり先頭の作業。

- 3文字表記の例。gccでは-trigraphsオプションが必要。
*/

int main(void)
??<
return (0);
??>

/*

2.1.5 多バイト文字とワイド文字 (Multibyte and Wide Characters)

- 非英語アルファベットに対応するためにワイド文字
とワイド文字列がある。

- ワイド文字とは、拡張文字集合(extended
character set)の要素のバイナリ表現である。

- 規格Cではワイド文字のためのwint_t型とwchar_t型
がある。

- ワイド文字は16ビットを占めるのが普通である。

- ナルワイド文字以外は、規格Cは拡張文字集合につい
て規程していない。

- ファイル等の外部メディアやCソースプログラムは、
大抵のところバイトの多きさの文字で構成されてい
る(バイト指向)。

- そのため多バイトコードという仕組みが使われる。
多バイトコードとは、バイトの大きさの文字の並び
とワイド文字の並びとの間でロケール固有の写像を
行う方法。

- 1つのワイド文字を、ソース文字集合または実行時文
字集合に含まれる文字(の並び)によって表現したの
が多バイト文字である。

- 多バイト文字は通常のC文字列である。

- 多バイト文字の形式や、多バイト文字とワイド文字
の写像は処理系定義である。

- 多バイト文字の方式には、状態依存型と状態独立型
がある。

- 規格Cは多バイト文字についていくつかの制約を課し
ている。

- 多バイト文字は次の場所で使ってよい。
- 注釈
- 識別子
- ヘッダ名
- 文字列定数
- 文字定数

- 「ソースの物理的表現の中の多バイト文字は、字句
解析、プリプロセス処理、それどころか継続行の接
合よりも前に認識され、ソース文字集合に翻訳され
なければならない。」 う、これわからない。。。

2.2 注釈 (Comments)

- お、Cでも//って使えるんだ。試すと、、、使えた。
*/

// Program to compute the squares of
// the first 10 integers
#include <stdio.h>
void Squares( /* no arguments */)
{
int i;
/*
Loop from 1 to 10,
printing out the squares
*/
for (i=1; i<=10; i++)
printf("%d //squared// is %d\n",i,i*i);
}

int main(void)
{
Squares();
return (0);
}

/*

2.3 字句 (Tokens)

- やっとTokenだ。。。

- 「Cプログラムを構成する文字は、以下に述べる規則
に従って集められて字句となる」うーん。名文だ。

- 字句は5種類。
- 演算子
- 分離子
- 識別子
- キーワード
- 定数

- 「コンパイラは左から右に文字を集めるとき、できる
だけ長い字句を作ろうとする。」

2.4 演算子と分離子 (Operators and Separators)

- 単純演算子
! % ^ など
- 複合代入演算子
+= -= など
- その他の複合演算子
-> ++ など
- 分離子
( ) [ ] など
- 代替綴り
<% %> <: :> など

2.5 識別子 (Identifiers)

- 識別子(名前)はラテン大文字と小文字、数字、アンダ
スコア、国際文字名および処理系定義の多バイト文字
の並びである。ということはgccでUTF-8の日本語も使
えるかも? 試す。
*/

#include <stdio.h>
int main(void)
{
int あ;
for (あ=1;あ<10;あ++)
printf("%d ", あ);

return (0);
}

/*

- これは駄目。-std=c99しても駄目。

- \uでいく。

*/

#include <stdio.h>
int main(void)
{
int \u3041; // あ
for (\u3041=1;\u3041<10;\u3041++)
printf("%d ", \u3041);

return (0);
}

/*

- おお。これは-std=c99ならOK。gccが多バイトからソー
ス文字集合への変換をしてくれないんだなきっと。

- 識別子の文字数の制限には注意が必要。特にリンカな
どの外部のプログラムに露出する場合は、そっちの制
限もあるから。

2.6 キーワード (Keywords)

- 識別子の一部は規格にてキーワードとして定められて
おり、普通の識別子として使ってはいけない。

- キーワードの一覧

auto _Bool break case char _Complex const
continue default do double else enum extern
float for goto if _Imaginary inline int long
register restrict return short signed sizeof
static struct switch typedef union unsigned
void volatile while

2.6.1 既定義識別子 (Predifined Identifiers)

- __func__は既定義識別子であり、プログラマが定義し
てはならない。


2.7 定数 (Constants)

- 別の言語ではリテラルとも言う。Cでは伝統的に
Constantsと呼ぶ。

- この節、型の概念とクロスリファレンスになっている
なぁ。まあしょうがないんだろうな。

2.7.1 整数定数 (Integer Constants)

- まあ、ごちゃごちゃと。割愛。

2.7.2 浮動小数点定数 (Floating-Point Constants)

- これも割愛。

2.7.3 文字定数 (Character Constants)

- 文字定数は、一つ以上の文字をアポストロフィで囲んで書く。

- 文字定数の前にLをつけるとワイド文字定数になる。

- 形式は次のとおり。

文字定数:
' c文字の並び '
L' c文字の並び '

c文字の並び:
c文字
c文字の並び c文字

c文字:
「'、 \ および改行を除く任意のソース文字集合の文字」
エスケープ表記
国際文字名

- 先頭にLを付けない文字定数はint型である。その値は、
実行時文字集合におけるその文字のコードである。

- Lで始まる文字定数はwchar_t型である。ワイド文字定
数は複数の文字と複数のエスケープ表記の並びとなっ
ていて、それらがひとつとなって多バイト文字を表す
のが普通。多バイト文字からから対応するワイド文字
への写像は処理系定義である。その変換を実行時に行
うのはmbtowc関数である。

- 複数文字定数の意味は処理系定義である。

2.7.4 文字列定数 (String Constants)

- 形式は次のとおり。

文字定数:
" s文字の並び "
L" s文字の並び "

s文字の並び:
s文字
s文字の並び s文字

s文字:
「"、 \ および改行を除く任意のソース文字集合の文字」
エスケープ表記
国際文字名

- n文字を含む文字列定数の型はchar [n+1]である。最
後はナル文字'\0'である。

- n文字を含むワイド文字列定数の型はwchar_t [n+1]で
ある。最後はナルワイド文字である。

- 文字列定数の文字を保持しているメモリの内容を書き
換えようとしてはいけない。

2.7.5 エスケープ表記 (Escape Characters)

- 割愛。

2.7.6 文字エスケープコード (Character Escape Codes)

- 割愛。

2.7.7 数値エスケープコード (Numeric Escape Codes)

- 割愛。

2.8 C++との互換性 (C++ Compatibility)

- 割愛。

2.9 文字集合、レパートリおよびコードについて (On Character sets, Repertories and Encodings)

- 国際文字名 (Universal Character Names)は、文字定
数、文字列定数および識別子の中に任意のUCS-2文字
またはUCS-4文字を書く書き方である。
- コードはISO/IEC 10646に従う。

2.10 練習問題 (Execises)

- 割愛。

*/

Cでの日本語の取り扱いを多少理解できた。

こつこつ。

2009年2月16日月曜日

【C:ARM5】1 入門


/*
1 入門

1.1 Cの進化
1.2 Cのどの方言を使うべきか?
1.3 Cプログラムの概観
1.4 規格合致性
1.5 構文記法


- 実行時ライブラリという言葉、気になるな。

- Standard C と Standard C++ の intersection を
Clean C と言うのか。

- 「普通、リンカはCに特化されていない。どのコン
ピュータシステムもリンカは一つで、さまざまな言
語で書かれたプログラムを処理する」なるほど。

- そうだとすると、リンカの入力となるオブジェクト
コードの構造も、コンピュータシステムにおいて単
一なのか?  ・・・単一なのだろう。ある言語がそ
うじゃないコードにコンパイルされたとしたら、そ
れはなんらかの仮想マシン上で動くものなのだろう。

- 規格合致性には二種類あるよ。規格合致ホスト処理
系であることと、規格合致フリースタンディング処
理系であること。後者は埋め込みシステムなど、質
素なターゲット環境向けなんだよ。

- 終端記号は、プログラム中に書かれたとき、そのと
おりに見えなければいけない記号だよ。なるほど、
こういう言い方もあるな。
*/

こつこつ。

【C:ARM5】「Cリファレンスマニュアル」を読む

Subversionのソースを見ていたら、Cの復習がしたくなった。そこで以前から読みたいと思っていたSteeleのCリファレンスマニュアルをちょっと覗いてみようと思う。Lispを知りつくしたSteeleが書くCの本ということでとても興味があったのだ。できれば、自分なりのCommon Lispとの対比も交えていきたい。