ポインタとアドレス
ポインタとアドレスについて詳しく解説します。
アドレスとは
アドレスとは、メモリ上の番地のことをいいます。メモリは、左から右に一直線に伸びている道路のようのものだと想像してみてください。そして、番地ごとに区画にわかれています。
64bitのアドレスが扱えるCPUでは、番地は「0」から「2の64乗 - 1」までです。単位はバイトです。0から始まります。
| | | | ... | | | 0 1 2 3 「2の64乗 - 2」「2の64乗 - 1」 (単位はバイト)
実際の物理メモリがこんなに広く利用可能かどうかはここではおいておいて、アプリケーションから見える仮想的なメモリは、このようなものだと想像してください。
これがアプリケーションのデータの保存できる領域です。作業をするためのとても大きな机のようなものですね。
ポインタとは
ポインタとは、アドレスを保存するための変数のことです。ポインタ変数とも呼ばれます。
「えっ、それだけ?」
はい。解説終了ですね(笑)。あとは、文法を覚えるだけですね。
ポインタ変数の宣言
ポインタ変数を宣言してみましょう。ポインタ変数を宣言するには「型名* 変数名;」とします。
// int32_tのポインタ型の変数の宣言 int32_t* num_ptr; // int64_tのポインタ型の変数の宣言 int64_t* num_ptr; // floatのポインタ型の変数の宣言 float* num_ptr; // doubleのポインタ型の変数の宣言 double* num_ptr; // struct myapp_bookのポインタ型の変数の宣言 struct myapp_book* book_ptr; // union myapp_valueのポインタ型の変数の宣言 union myapp_value* value;
規則をみてください。実際の型名の後ろに「*」をつけます。
int32_tのポインタ型, int64_tのポインタ型、floatのポインタ型、doubleのポインタ型、構造体のポインタ型、共用体のポインタ型のサンプルを書いてみました。
以下のように、「*」を変数名の直前に記述している参考書もあるかと思います。これは、これで正しいです。
int32_t *num_ptr;
C99対応のC言語入門では「*」の理解を、一貫してポインタ型として扱います。これは、ある意味では、厳密に正しいC言語の解説ではないのではないかと感じる面もありますが、実用上で、矛盾が生じることはないですし、理解が簡単です。
アドレスの取得
アドレスを取得するにはアドレス演算子「&」を使用します。
&変数名
アドレスを取得して、ポインタに代入してみましょう。int32_tのポインタ、doubleのポインタ、構造体のポインタの例を書いてみます。アドレスの値はprintf関数の「%p」フォーマット指定子で出力できます。
#include <stdint.h> #include <stdio.h> // 構造体 struct myapp_book { int32_t id; const char* name; int32_t price; }; int main(void) { // int32_tの実際の値 int32_t numi = 5; // int32_tのポインタ型のポインタを宣言して、アドレスを取得して代入 int32_t* numi_ptr = &numi; // アドレスを出力 printf("numi_ptr %p\n", numi_ptr); // doubleの実際の値 double numd = 2.5; // doubleのポインタ型のポインタを宣言して、アドレスを取得して代入 double* numd_ptr = &numd; // アドレスを出力 printf("numd_ptr %p\n", numd_ptr); // myapp_bookの実際の値 struct myapp_book book = {id:1, name: "C99 Book", price : 1500}; // myapp_bookのポインタ型のポインタを宣言して、アドレスを取得して代入 struct myapp_book* book_ptr = &book; // アドレスを出力 printf("book_ptr %p\n", book_ptr); }
僕の環境での出力結果です。16進数でアドレスが出力されています。
numi_ptr 0x7fff6d615824 numd_ptr 0x7fff6d615818 book_ptr 0x7fff6d615800
ポインタから実体を取り出す
ポインタから実際の値(実体と呼びます)を取り出してみましょう。これは、アドレスを取得する操作と逆の操作になります。
実体を取り出すには変数名の前に「*」をつけます。
*ポインタ変数
実体を取得するサンプルを書いてみます。
#include <stdint.h> #include <stdio.h> // 構造体 struct myapp_book { int32_t id; const char* name; int32_t price; }; int main(void) { // int32_tの実際の値 int32_t numi = 5; // int32_tのポインタ型のポインタを宣言して、アドレスを取得して代入 int32_t* numi_ptr = &numi; // 実体を取得して出力 int32_t numi_real = *numi_ptr; printf("numi_real %d\n", numi_real); // doubleの実際の値 double numd = 2.5; // doubleのポインタ型のポインタを宣言して、アドレスを取得して代入 double* numd_ptr = &numd; // 実体を取得して出力 double numd_real = *numd_ptr; printf("numd_real %f\n", numd_real); // myapp_bookの実際の値 struct myapp_book book = {id:1, name: "C99 Book", price : 1500}; // myapp_bookのポインタ型のポインタを宣言して、アドレスを取得して代入 struct myapp_book* book_ptr = &book; // 実体を取得して出力 struct myapp_book book_real = *book_ptr; printf("book_real id: %d, name: %s, price: %d\n", book_real.id, book_real.name, book_real.price); }
出力結果です。実際の値になっています。
numi_real 5 numd_real 2.500000 book_real id: 1, name: C99 Book, price: 1500
ポインタから直接、構造体のメンバ変数へアクセス
ポインタから直接、構造体のメンバ変数へアクセスするには「.」の代わりに「->」を使用します。
#include <stdint.h> #include <stdio.h> // 構造体 struct myapp_book { int32_t id; const char* name; int32_t price; }; int main(void) { // myapp_bookの実際の値 struct myapp_book book = {id:1, name: "C99 Book", price : 1500}; // myapp_bookのポインタ型のポインタを宣言して、アドレスを取得して代入 struct myapp_book* book_ptr = &book; // アドレスを出力 printf("id: %d, name: %s, price: %d", book_ptr->id, book_ptr->name, book_ptr->book_ptr %p\n", book_ptr); }
実務で書く場合のポインタの変数名はどんな感じですか?
実務で書く場合は「book_ptr」のように「_ptr」をつけることは、あまり多くありません。僕の場合は、明示的にポインタであることを示したい例外を除いて「_ptr」はつけないですね。
// 構造体を動的にメモリ確保する場合の変数名のサンプル struct myapp_book* book = calloc(sizeof(struct myapp_book), 1);
calloc関数とsizeof演算子を使って、構造体のデータのメモリ確保を行って、構造体のポインタに代入しています。
ポインタ演算はどこで解説されていますか?
ポインタ演算は、近く解説予定です。
汎用ポインタはどこで解説されていますか?
汎用ポインタは、近く解説予定です。