LoginSignup
1
3

More than 3 years have passed since last update.

C言語のポインタについて(1章〜2章)

Last updated at Posted at 2019-11-17

はじめに

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); 

静的変数は仮想アドレス上で、固有の領域を持つ

・関数やグローバル変数は名前が同じであれば、ソースファイルをまたいでも同じものっとして扱われる(「リンカ」)

自動変数は領域を使いまわしするため、スタックに積む

  1. mainで使用される自動変数を宣言順に積む
  2. mainから呼び出される関数(関数x)の引数を後ろから順に積む
  3. 関数xのリターン用の復帰情報(アドレスなど)を積む
  4. 関数xのアドレスにジャンプする
  5. 関数xで使用される自動変数を宣言順に積む
  6. 関数xの実行中計算途中の値を積む
  7. 関数xの終了時、ローカル変数の値を開放する
  8. 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

1
3
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3