はじめに
この記事は「詳説 Cポインタ」を読んで
忘れそうなことや重要だったことのメモです。
自分用のメモなのでかなり読みにくいかもしれませんが
ご了承ください。
詳しく学びたい方はぜひご購入ください。
本の全体像
この本ではポインタについて徹底的に掘り下げられています。
第1章 ポインタの復習
- メモリは3つに分類される
- 静的(static) / 大域(Global)
- プログラム開始時に割り当てられる
- プログラム終了まで維持
- 自動
- 関数の中で定義される
- 関数が実行されている間のみ存在する
- 動的
- ヒープから割り当てられる
- 解放されるまで存在する
- 静的(static) / 大域(Global)
- null
- NULLマクロの定義 #define NULL ((void *)0)
- voidへのポインタ
- charへのポインタと同じ表現、メモリ上の並び
- 基本的に他のポインタと等しくなることはない
- 2つのvoidへのポインタがNULLのとき等しいとされる
- sizeof(void *)は問題ないが、sizeof(void)は不正
- ポインタの大きさは、コンピュータの種類とコンパイラによって決まる
- const
- 定数へのポインタ(const int *)
- 間接参照(*x)はOK
- 間接参照で値変更はNG
- しかしポインタ自身は定数ではないため書き換えることができる
- 定数へのポインタ(const int *)
変数名 | 値 | ポインタ |
---|---|---|
int | O | - |
int * | O | O |
const int | X | - |
const int * | O | X |
int *const (定数ポインタ) | X | O |
const int *const (定数への定数ポインタ) | X | X |
(変更できる: O , 変更できない : X)
- const int * = int const *どちらでも可
第2章 C言語の動的メモリ管理
- 解放済みポインタにはNULLを代入し、ぶら下がりポインタをつくらない
- プログラム終了時のメモリ解放
- OSによってはメモリをOSの責任で管理するものと、プログラムの責任で管理するものがある
- すべてのメモリを解放するとプログラムや実行時間が長くなったり、バグが増えるなどのデメリットもある
- OSの責任でメモリを管理する場合は、メモリを解放するメリットとして再利用することがあげられる
- プログラム終了後はOSがリソースを回収するため、メモリを解放する理由はあまりない
第3章 ポインタと関数
- 使う場面
- 関数へのポインタ渡し
- 関数のポインタ
- 定数へのポインタを関数の引数に渡すことで、関数の中でデータ変更することを許容しない
- 関数ポインタ
- 関数のアドレスを持っているポインタ
- 関数の名前自身は関数のアドレスとして評価される
- 関数ポインタは別の型の関数ポインタにキャストできる
- ただし適切な引数が与えられているか確認しないため慎重に行うこと
- 関数ポインタで遊んでみる ( おまけ )
#include <stdio.h> typedef int (*operation)(int, int); static operation operations[128] = {NULL}; int add(int n1, int n2){return (n1 + n2);} int sub(int n1, int n2){return (n1 - n2);} void initOperations() { operations['+'] = add; operations['-'] = sub; } int calc(char opcode, int n1, int n2) { operation f = operations[opcode]; return f(n1, n2); } int main() { operation operations[128] = {NULL}; initOperations(operations); int n1 = 4; int n2 = 2; printf("%d\n", calc('+', n1, n2)); printf("%d\n", calc('-', n1, n2)); return (0); }
足し算と引き算を計算できる、シンプルなプログラム
関数ポインタを使うことで計算の切り替えを綺麗に書ける
第4章 ポインタと配列
2次元配列のイメージ
int matrix[2][3] = {{1,2,3},{4,5,6}}; のとき...
index | 値 | ポインタ |
---|---|---|
matrix[0][0] | 1 | 0x7fffd297af20 |
matrix[0][1] | 2 | 0x7fffd297af24 |
matrix[0][2] | 3 | 0x7fffd297af28 |
matrix[1][0] | 4 | 0x7fffd297af2c |
matrix[1][1] | 5 | 0x7fffd297af30 |
matrix[1][2] | 6 | 0x7fffd297af34 |
上記の例は 連続的であるが
メモリが連続することは保証できない
たいていの場合連続になるが、ヒープの状態に依存する
(例)
(const int) { 100 } (int[3]) { 1,2,3 }
- ジャグ配列
- 各行が異なる列数で構成される2次元配列の一種
(例)
int (*(arr2[])) = { (int[]) {0,1,2,3}, (int[]) {4,5}, (int[]) {6,7,8}, };
第5章 ポインタと文字列
- C言語の文字列は2種類ある
- バイト文字列 (char)
- ワイド文字列 (wchar_t)
- 16または32ビットの文字であるワイド文字のための型
コンパイラによって、文字列定数が変更される場合がある
#include <stdio.h> int main(void){ char *x = "AAA"; *x = 'B'; printf("%s\n",x); // コンパイラによってはBAAとでる return (0); }
const を付けて定数にすると安全
const char *x = "AAA";
文字列の領域について
#include <stdio.h> #include <stdlib.h> #include <string.h> int main(void){ char *a1 = "42"; char *a2 = "42"; char *b1 = (char *)malloc(sizeof(char) * strlen("42")); char *b2 = (char *)malloc(sizeof(char) * strlen("42")); char c1[] = "42"; char c2[] = "42"; printf("a1 = %p, a2 = %p\n",a1,a2); // リテラルプール printf("b1 = %p, b2 = %p\n",b1,b2); // ヒープ printf("c1 = %p, c2 = %p\n",c1,c2); // スタック return (0); }
実行結果
a1 = 0x55dfb685d004, a2 = 0x55dfb685d004 b1 = 0x55dfb81b62a0, b2 = 0x55dfb81b62c0 c1 = 0x7ffedc9952e2, c2 = 0x7ffedc9952e5
上から順に
リテラルプール, ヒープ , スタック
の位置で確保される
第6章 構造体とポインタ
- 構造体用メモリの割り当てと解法を繰り返すと、オーバーヘッドが生じパフォーマンスが悪くなる場合がある
- 保存場所を用意することでオーバーヘッドを小さくできる
本記事には載せませんが, リンクリストの図が非常にわかりやすかったです
第7章 セキュリティの問題と不適切なポインタの使用
- アドレス空間配置のランダム化 (ASLR)
- メモリのデータをランダムに配置 (コード、スタック、ヒープ)
- マクロ宣言よりtypedefのほうが良い
- コンパイラの有効範囲チェックルールが適用される
- ポインタの初期化忘れの確認
- assert(p!=NULL)がおすすめ
- 条件式が偽だった場合プログラムが終了する
- セキュリティに関する問題の多くは、バッファオーバーフローが原因
- 割り当てられたメモリの範囲外にアクセス
- DoS攻撃 (2種類ある)
- 境界付きポインタ
- 有効な範囲でだけ使用できるポインタ
- 配列の要素以外をアクセスできないように条件分岐で指定
- 配列はメモリ上連続した領域に割り当てられることが保証されている
- しかし、構造体は上記が保証されていないためポインタの算術演算は行うべきではない
- たいていのOSはプログラム終了時にメモリを0で上書きなどの処理はしない
- したがって解放する前にmemsetなどでデータをクリアにするべき
第8章 残りの話題
(例)
0x12345678 の場合
78から格納するのがリトルエンディアン
12から格納するのがビックエンディアン
- 型パンニング
- ある方から別の型に変換する際に、unionなどを使って型システムを壊す手法
- スレッド間でポインタを共有する場合はミューテックスを用いてデータの保護をする
- 不透明ポインタ
- データをカプセル化する
以下の記事が参考になりました
- ポリモーフィズム
- あるメソッドに対しオブジェクトがそれぞれで異なる動作をする
以下の記事が参考になりました
感想
1章から6章はポインタの基礎+αという感じでした。
網羅的に一度学びなおしたい人には良いかと思います。
また6章ではリンクリストやキュー、スタックなどの実装の説明がありました。
上記の実装の勉強をしたい方は是非読んでみましょう
7章のセキュリティ関連の話題も面白かったですが
具体的な攻撃の方法が書かれていなかったのが残念でした...
ほかの本と比べ、より専門的な内容となっていますが
ポインタをより詳しく知りたい方は是非読んでみてください。