はじめに
「C言語ポインタ完全制覇」の内容をもとにC言語の復習。 この本は2000年ぐらいに買ったもの。当時十分C言語の開発経験はあったつもりだったけど、はっきり言って、この本の内容を当時の自分は理解してなかった。 この本は、初心者の2冊目の本として、もしくは中級者の知識の総点検として非常にお勧めだと思う。
メイン関数について
int main(void) int main(int argc, char*argv[])
これ以外は間違い。
配列について
添え字演算子は配列とは無関係
- 式の中では、配列は「その先頭要素へのポインタ」に読み替えられる。
- p[i]は、*(p + i)の読み替え
関数の仮引数の宣言
- 関数の仮引数の宣言の場合に限り、配列の宣言はポインタに読み替えられる。
- ほかの普通の宣言のときは配列として宣言されることに注意。
多次元配列
int hoge[3][5];へアクセスするとき、何が起きてるかの解説。
引用 p175
1 hogeの型は「int配列(要素数5)の配列(要素数3)」である。 2 だが、式の中なので、配列はポインタに読み替えられる。よってhogeの型は「intの配列(要素数5)へのポインタ」となる。 3 hoge[i]は、*(hoge + i)のシンタックスシュガーである。 1 ポインタにi加算することは、そのポインタが指す型のサイズiだけ進む。 2 *(hoge + i)の*により、ポインタがひとつはがされる。*(hoge + i)の型は「intへの配列(要素数5)」となる。 3 が、式の中なので、配列がポインタに読みかえれらる。*(hoge + i)の最終的な型は「intへのポインタ」となる。 4 (*(hoge + i))[j]は、*((*(hoge + i)) + j)に等しい。したがって、(*(hoge + i))[j]は「intへのポインタにjだけ加算した アドレスの内容であり、型はintである。
メモリ確保
-
mallocの戻り値の型はvoid*だ。
- したがって戻り値をキャストする必要はない。
-
cmalloc/cfree
- callocを使ってメモリ領域をゼロクリアするよりも、自前で0xccなどで埋めたほうがバグを発見しやすい。
-
reallc
- 引数で指定したポインタのメモリ領域を広げる。その領域の後ろが開いてれば単に広げるだけだが、開いてなければ別の領域に新たにコピーするため重い。
- javaのコレクションもこれを使ってると思われ。javaの場合は最初にある程度ガバッと領域確保し、それが足りなくなったらまたガバッという感じで確保するのだろう。Javaがメモリ食いまくりの理由の一員はこれもあると思う。
アライメント
コンピュータのワード境界に合わせて以下のような詰め物をよくすると思う。
typedef struct { int int1; char pad1[4]; double double1; char char1 char pad2[7] double double2; } Hoge;
こんなことをしなくてもコンパイラは勝手にアライメントをあわせてくれるという。 しかしこれはターゲットによると思う。昔、組み込み系の開発やったけど、パディングしないと アドレスエラーが出てしまいバグの原因となった。まぁコンパイラがしょぼかったのかもしれないが。
宣言の解読
基本
手順
引用 p144
--ここから--
- まず識別子(変数名または関数名)に着目する。
-
識別しに近いほうから、優先順位に従って派生型(ポインタ、配列、関数)を解釈する。優先順位は以下のようになっている。
- 宣言をまとめるための括弧
- 配列を意味する[]、関数を意味する()、
- ポインタを意味する*
- 派生型を解釈したら、それを「of」または「to」または「returning]で連結する。
- 最後に、型指定子(左端にある、intとかdoubleとか)を追加する。
- 英語が苦手な人は、順序を逆にし、日本語で解釈する。 --ここまで--
宣言解読の例
引用(一部要約) p145
--ここから--
C言語 | 英語的表現 |
int hoge; | hogeis int |
int hoge[10]; | hoge is array(size 10) of int |
int hoge[10][3] | hoge is array(size 10) of array(size3) of int |
int *hoge[10]; | hoge is array(size 10) fo pointer to int |
double (*hoge)[3]; | hoge is pointer to array(size 3) of double |
int func(int a); | func is function(arg int) returning int |
int (*func_p)(int a); | func_p is pointer to function (arg int a) returning int |
--ここまで--
宣言に関しては以下が読めれば卒業だな。慣れてないと難しいよ(p199)。
void (* signal(int sig, void (*func)(int)) ) (int);
答え
signal is function(sig is int, func is pointer to function(int) returning void) returning pointer to function(int) returning void singnalは「voidを返し引数がintの関数へのポインタ」を返す関数で、その引数は2つあり、1つは、int, もう1つは、「voidを返し引数がintの関数へのポインタ」である。
ポイントは、戻り値が関数へのポインタであること。これが異常に複雑になる原因。 以下のようにtypedefすればかなりわかりやすくなる。
typedef void(*sig_t)(int); sig_t signal(int sig, sig_t func);
const
引用 p180
--ここから--
- 「Cの宣言を解読する」で述べた規則に従い、識別子から初めて、順に外側に英語で宣言を解読していく。
- 解読した部分の左側にconstが出現したら、そこで「reado-only」を追加する。
- 解読した部分の左側に型指定子が出現し、さらにその左側にconstがある場合、型指定子をとりあえず飛ばしてread-onlyを追加する。
- 英語が苦手な人は、constがその直後の単語を修飾していることに注意しながら、日本語に訳す。 --ここまで--
例
宣言 | 英語 |
char * const src | src is read-only pointer to char |
char const *src | src is pointer to read-only char |
const char *src | 同上 |
型のサイズ計算
定期的に読めるかチェック。
引用(一部要約) p159
--ここから--
宣言 | サイズ |
int hoge; | 4byte |
int hoge[10]; | 4 x 10 = 40byte |
int *hoge[10]; | 4 x 10= 40byte |
double *hoge[10] | 4 x 10 = 40byte |
int hoge[2][3]; | 4 x 3 x 2 = 24byte |
--ここまで--