目次
C言語への導入
- 原典としてのK&R = プログラミング言語C 第2版 ANSI規格準拠
- 「高級アセンブラ」としてのC言語 (gcc -S)
- call by value に徹する:ポインタが必要となる
- コンパクトな言語コア:libc, libm (-lm)
- 元々はOSを高級言語で実装するための贅沢な言語、UNIX システムのインタフェース
- 標準入出力(言語の機能ではなく「標準化された拡張機能」の一部)
- 応用:シェルプログラミングの部品 / Windows API / 組み込み / スクリプト言語の機能拡張
- C++ や Objective-C との関連
- コンパイラの機能:警告 -Wall とデバッグモード (-DDEBUG)
- プリプロセッサ(gcc -E)、リンカ、静的リンクと動的リンク
- K&R と ANSI-C / 最新は ISO の C99
- プロトタイプ宣言、型チェックの厳格化
- メモリリークとポインタエラー、セキュリティ ElectricFence
- 配列=ポインタ計算のシンタックスシュガー
- malloc と free : 大きなメモリは動的に割り当てる
- C言語における文字列データ
- string.h :文字コードの配列を文字列データっぽく扱う
- バックスラッシュと文字コード問題
- インデントの流儀、名前の付け方の流儀(標準ライブラリ流、Microsoft流など)
- make : 常にコンパイルが通る状態を保つ
オンラインマニュアルの利用
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 機能も条件コンパイルの応用である。