目次

C言語への導入

オンラインマニュアルの利用

Ubuntu で man scanf などとオンラインマニュアルを参照するためには

$ sudo aptitude install manpages-dev

ちなみに man printf は /usr/bin/printf の説明。stdio の関数を調べたければ man 3 printf のように「マニュアルの章番号」を指定する。

main関数の返り値

Unix システムにおいて C 言語のプログラムは、シェルプログラミングにおける部品としての側面がある。

この観点から、B shell とのインタフェースに着目してみる。

最もコンパクトなC言語のプログラムには、ただmain関数が必要である。

$ cat hello.c
int main(void)
{
  return 0;
}

$ gcc hello.c -o hello

$ ./hello && echo "ok"
ok

Linux であれば実行ファイルは hello になる。Cygwin では hello.exe である。 いずれにせよ main 関数の返り値が 0 であればシェル変数 $? に「直前のプログラムの終了コード」として 0 が代入される。

終了コード 0 は正常終了の意味なので、&& 以降のコマンドが実行される。

以下のように return 1 とすると && 以降は実行されない。

$ cat hello.c
int main(void)
{
  return 1;
}

$ gcc hello.c -o hello

$ ./hello && echo "ok"

main関数の引数の数

慣習として引数の1つめにはargcと名付ける。argment count を意味する。

int main(int argc)
{
  return argc;
}

ほとんど無意味なサンプルだが、終了コードを echo で出力してみると下記の通り。

$ ./hello ; echo $?
1

$ ./hello a ; echo $?
2

$ ./hello a b ; echo $?
3

引数を与えなくても1が返ってくるが、 これは "./hello" というプログラムの名前そのものが1つめの引数のように扱われるからである。

main関数の引数の中身

仕様では第2引数の型は

char **argv

とされる。名前は慣習で argv (argument value) とする。

 *argv  :   argv  をポインタと見なしてそれが指す値
**argv  : (*argv) をポインタと見なしてそれが指す値 => それが char 型
int main(int argc, char **argv)
{
  return **argv;
}
$ ./hello ; echo $?
46

ちなみに *(*argv+1) だと 47 に、*(*argv+2) だと 104 になる。

これらは「アスキーコード表」で調べることができる。 ここでは python の力を借りて確認してみると

$ python
>>> chr(46)
'.'
>>> chr(47)
'/'
>>> chr(104)
'h'

めでたく "./hello" の一部であるらしいことが分かった。

念のため 'h' の文字コードが 104 であることを C 言語でも確認してみよう。

int main(void)
{
  return 'h';
}
$ ./hello ; echo $?
104

ポインタと配列

ポインタ     ポインタ     文字コード
argv      =>  *argv    =>  * *argv    '.'
              *argv+1  =>  *(*argv+1) '/'
              *argv+2  =>  *(*argv+2) 'h'
              *argv+3  =>  *(*argv+3) 'e'
              *argv+4  =>  *(*argv+4) 'l'
              *argv+5  =>  *(*argv+5) 'l'
              *argv+6  =>  *(*argv+6) 'o'

C言語におけるポインタの計算は配列を使うと簡単になる。 この場合は

  **argv       argv[0][0]
  *(*argv+1)   argv[0][1] 
  *(*argv+2)   argv[0][2]

のように書き換えることができる。

ポインタ     ポインタ       文字コード
argv     =>  argv[0]    =>  argv[0][0]    '.'
             argv[0]+1  =>  argv[0][1]    '/'
             argv[0]+2  =>  argv[0][2]    'h'

ちなみに * の逆の演算を行う & という演算子がある。

argv == &(argv[0])

文字コードの配列としての文字列

では argv[0][6] が 'o' として、インデックス 7 の中身はどうなっているだろう?

int main(int argc, char **argv)
{
  return argv[0][7];
}
$ ./hello ; echo $?
0

文字コード 0 が文字列の区切りを表すことが確認できた。

ポインタの配列

2つめの文字列は?

int main(int argc, char **argv)
{
  return argv[1][0];
}
$ ./hello abc; echo $?
97

97は'a'の文字コードである。

  *(*(argv+1)  )  =  argv[1][0]  = 97 => 'a'
  *(*(argv+1)+1)  =  argv[1][1]  = 98 => 'b'
  *(*(argv+1)+2)  =  argv[1][2]  = 99 => 'c'

のように書き換えることができる。

ポインタ     ポインタ       文字コード
argv+1   =>  argv[1]    =>  argv[1][0]  = 'a'
             argv[1]+1  =>  argv[1][1]  = 'b'
             argv[1]+2  =>  argv[1][2]  = 'c'

ポインタはメモリ上のアドレスを表しているので、32bit のシステムでは一般に4バイトを使う。

だがこの場合も argv+1 を計算すると4バイト隣のアドレスが得られる。 argv[1] によってそのポインタが指す値が得られる。

プリプロセッサ

プリプロセッサは「マクロの定義」「インクルード」「条件コンパイル」などの用途で用いられる。

まず定数をマクロで定義する例。

#define NUM 100
int main(int argc, char **argv)
{
  return NUM;
}
$ gcc -E hello.c
# 1 "hello.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "hello.c"

int main(int argc, char **argv)
{
  return 100;
}

続いて条件コンパイルの例。 一般的には機種依存のコードを一つのソースファイルにまとめたり、 デバッグ時のみ実行する処理を書くために用いられる。

#ifdef DEBUG
# define NUM 1
#else
# define NUM 100
#endif
int main(int argc, char **argv)
{
  return NUM;
}
$ gcc -E hello.c
# 1 "hello.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "hello.c"

int main(int argc, char **argv)
{
  return 100;
}
$ gcc -E -DDEBUG hello.c
# 1 "hello.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "hello.c"

int main(int argc, char **argv)
{
  return 1;
}

標準ライブラリに含まれる assert 機能も条件コンパイルの応用である。