はじめに
C言語のポインタについて理解を深めるため、この本を読み備忘録としてまとめる
C言語ポインタ完全制覇
結論から言えば、この本は間違いなく良書と言える。
私は、ポインタの使い方を意味が分からず記憶していたが、
この本で何故そうなるのかという点がわかり、大変感謝している。
1章
C言語について
・もともとUNIX系のOS開発用の言語
ポインタについて
「ポインタ型」というのが存在する
・つまり、int型の変数や値があるように、
ポインタ型の変数や値も存在する
変数のアドレスの確認
・変数に「&」を適用すると変数のアドレスを表す
・変数に「*」を適用するとポインタ(アドレス)の指す先の値を表す
int hoge = 5;
int piyo = 10;
int *pointer = &hoge;
printf("&hoge..%p\n", &hoge);
printf("&piyo..%p\n", &piyo);
printf("pointer..%p\n", pointer);
・以下のような図で考えると分かりやすい
アドレス | 変数名 | 値 |
---|---|---|
0x7ffee27a4b38 | pointer | 0x7ffee27a4b48 |
〜 | ||
0x7ffee27a4b44 | piyo | 20 |
〜 | ||
0x7ffee27a4b48 | hoge | 10 |
配列
・式の中では、配列名は「その配列の先頭要素へのポインタ」に読み替えられる
・以下の値はすべて同じである。
配列hogeの先頭配列のアドレスの&hoge[0]
配列hogeの先頭を表す配列名hoge
変数名hogeのアドレスを表す&hoge
p[i] は *(p + i) と同義である
int hoge[5] = {1,2,3,4,5};
int *pointer = hoge;
printf("hoge[0]...%p\n", &hoge[0]);
printf("hoge...%p\n", &hoge);
printf("hoge...%p\n", hoge);
printf("hoge[1]...%p\n", &hoge[1]);
printf("hoge[2]...%p\n", hoge + 2);
void型のポインタ変数はどこまで取り出せば良いか分からない
・アドレスの代入時は開始場所の代入なので問題ないが、
実際にアドレスから取り出す場合は、メモリから取り出す範囲を指定するため、
型の定義をする必要がある
int hoge = 5;
void *pointer;
pointer = &hoge;
// printf("%d\n", *pointer); //??型のポインタ → 取り出せない
printf("%d\n", *(int *)pointer); //int型のポインタを指す変数
ポインタ変数のバイト数の確認
int hoge = 5;
int *pointer = &hoge;
printf("pointer: %ld", sizeof(pointer));
ポインタ変数の「+n」
・ポインタにn加算すると、ポインタの指す型のサイズ×n進む
・int型は基本的に4バイト(32bit)である
NULLポインタ
・何も指していないことを保証する
・変数を宣言しただけでは、値は不定となるため、
初期化時に宣言すること
int *pointer;
int *pointer2 = NULL;
仮引数では int func (int *a) と int func (int a[]) は同じ
・次の書き方は同じ意味を指す
int get_word(char *buf, int buf_size, FILE *p)
int get_word(char buf[], int buf_size, FILE fp[])
2章
仮想アドレス
・同じソースコードを別窓で実行すると、hogeのアドレスが同じだが、
値はそれぞれで異なる。
・アプリケーション実行時のアドレスは、物理的な領域のアドレスではない。
アプリケーションはプロセスごとに独立している
int hoge;
char buf[256];
printf("&hoge....%p\n", &hoge);
fgets(buf, sizeof(int), stdin);
sscanf(buf, "%d", &hoge);
for(;;){
printf("hoge..%d\n", hoge);
getchar();
hoge++;
}
return 0;
・1つめの窓
&hoge..0x62cc4c
9
hoge..9 //Enter
hoge..10 //Enter
hoge..11
・2つめの窓
&hoge..0x62cc4c
5
hoge..5 //Enter
hoge..6 //Enter
hoge..7
Cの変数で記憶領域の寿命は3種類ある
1.静的変数(グローバル変数、ファイル内でstatic指定した変数)
→ プログラムの開始から終了まで
2.動的変数(関数内でstatic指定をしていない変数)
→ 関数のブロックに入った時からブロックを抜けるまで
3.malloc()で確保した領域
→ malloc()関数のコールからfree()関数をコールするまで
関数(本体)と文字列リテラルは書き込み禁止領域
・関数はそもそも書き換えることはない
・文字列は「charの配列」となり先頭文字へのポインタに読み替られる。
int main(void){
char *a = "abc";
printf("%s\n", a);
a[0] = 'd';
printf("%s\n", a);
}
・この場合、1回目の文字列表示後
Segmentation fault (コアダンプ)となり、正しく表示されない
関数にもアドレスがある
・関数は式の中では「関数へのポインタに」読み変えられる
void func1(void){
}
void func2(void){
}
int main(void){
printf("func1...%p\n", &func1);
printf("func2...%p\n", &func2);
}
・メモリに格納される内容の順番
アドレス | 変数名 |
---|---|
0x401180 | func1 |
0x401186 | func2 |
関数へのポインタの書き方
・使い方の例
int func (int d){
}
int main(void){
int (*func_p)(int);
func_p = func;
func(2);
}
・使い方は、関数への配列のポインタなど
int (*func_table[])(double) = {
func0,
func1,
func2,
};
func_table[i](1);
静的変数は仮想アドレス上で、固有の領域を持つ
・関数やグローバル変数は名前が同じであれば、ソースファイルをまたいでも同じものっとして扱われる(「リンカ」)
自動変数は領域を使いまわしするため、スタックに積む
- mainで使用される自動変数を宣言順に積む
- mainから呼び出される関数(関数x)の引数を後ろから順に積む
- 関数xのリターン用の復帰情報(アドレスなど)を積む
- 関数xのアドレスにジャンプする
- 関数xで使用される自動変数を宣言順に積む
- 関数xの実行中計算途中の値を積む
- 関数xの終了時、ローカル変数の値を開放する
- mainで関数xの引数をスタックから除去する
mallocはヒープ領域に積む
・mallocは指定されたメモリサイズを確保し、先頭へのポインタを返す
・動的にメモリを割り当て、任意の順序で解放できる記憶領域を「ヒープ領域」という
1.構造体(メンバを含め)を動的に確保できる
・構造体の作成する個数
・構造体のメンバの要素の長さ
typedef struct BookData_tag{
char *title;
int price;
char isbn[32];
struct BookData_tag *next;
}BookData;
int main(void){
BookData *book_data_p;
book_data_p->title = malloc(sizeof(char) * 32);
strcpy(book_data_p->title, "aaa"); //用意したアドレスに文字を代入
//book_data_p->title = "aaa"; 文字列aaaを確保してそのアドレスを入れている
// そのため、確保したメモリは永久に参照されない(メモリリーク)
book_data_p->price = 32;
book_data_p->isbn[0] = 'a';
printf("%s\n",book_data_p->title);
printf("%d\n",book_data_p->price);
}
2.mallocの仕組みはOSから一括して大きなメモリを取得して分ける
・freeした領域は、すぐにメモリを解放されるわけではないが、参照してはいけない
・malloc,freeを繰り返すと、フラグメンテーション(断片化)する。
・対策はメモリコンパクション
3.calloc,cfreeは使わないほうが良い
・callocで確保した領域はすべてのビットがゼロに初期化されるが、浮動小数点ゼロ、
または NULL ポインタ定数の表現と同じであるとは限らないため、再初期化が必要なことがある。
・cfreeに関しては、通常のfree関数と同じ仕様のため、私は特別使用しようとおもいませんでした。
アラインメント
・型のサイズの都合上、どうしても埋め込み必要な領域
・下記の構造体は計21バイトだが、charの箇所に3バイト分アラインメント
があり、計24バイトになっている。
typedef struct{
int int1; //4バイト
double double1; //8バイト
char char1; //1バイト
double double2; //8バイト
} Hoge;
あと書き
1.2章の読破時間・・・6h