C Language
ポインタ
サーチ…
前書き
ポインタとは、別のオブジェクトや関数のアドレスを格納できる変数の一種です。
構文
- <データ型> * <変数名>;
- int * ptrToInt;
- void * ptrToVoid; / * C89 + * /
- struct someStruct * ptrToStruct;
- int ** ptrToPtrToInt;
- int arr [length]; int * ptrToFirstElem = arr; / * <C99の場合、 'length'はコンパイル時定数でなければなりません。> = C11の場合は1にする必要があります。 * /
- int * arrayOfPtrsToInt [length]; / * <C99の場合、 'length'はコンパイル時定数でなければなりません。> = C11の場合は1にする必要があります。 * /
備考
アスタリスクの位置は、定義の意味には影響しません。
/* The * operator binds to right and therefore these are all equivalent. */
int *i;
int * i;
int* i;
ただし、一度に複数のポインタを定義する場合は、それぞれに独自のアスタリスクが必要です。
int *i, *j; /* i and j are both pointers */
int* i, j; /* i is a pointer, but j is an int not a pointer variable */
ポインターの配列も可能です。アスタリスクは配列変数の名前の前に付いています:
int *foo[2]; /* foo is a array of pointers, can be accessed as *foo[0] and *foo[1] */
一般的なエラー
ポインタの不適切な使用は、多くの場合、セグメンテーション違反のためにセキュリティバグやプログラムクラッシュを含む可能性があるバグの原因となります。
割り当ての失敗をチェックしない
メモリ割り当ては成功するとは保証されず、代わりにNULL
ポインタを返すことがありNULL
。戻り値を使用して、割り当てが成功したかどうかを確認せずに、 未定義の動作を呼び出します。これは通常クラッシュにつながりますが、クラッシュが発生するという保証はなく、そのことに頼っても問題につながる可能性があります。
たとえば、安全でない方法:
struct SomeStruct *s = malloc(sizeof *s);
s->someValue = 0; /* UNSAFE, because s might be a null pointer */
安全な方法:
struct SomeStruct *s = malloc(sizeof *s);
if (s)
{
s->someValue = 0; /* This is safe, we have checked that s is valid */
}
メモリを要求するときにsizeofの代わりにリテラル番号を使う
与えられたコンパイラ/マシン構成では、型のサイズは既知です。ただし、( char
以外の)指定された型のサイズは、すべてのコンパイラ/マシン構成で同じになることを定義する標準はありません。コードがメモリ割り当てにsizeof(int)
代わりに4を使用すると、元のマシン上で動作する可能性がありますが、コードは必ずしも他のマシンやコンパイラに移植する必要はありません。型の固定サイズは、 sizeof(that_type)
またはsizeof(*var_ptr_to_that_type)
置き換えてください。
移植不可能な割り当て:
int *intPtr = malloc(4*1000); /* allocating storage for 1000 int */
long *longPtr = malloc(8*1000); /* allocating storage for 1000 long */
ポータブル割り当て:
int *intPtr = malloc(sizeof(int)*1000); /* allocating storage for 1000 int */
long *longPtr = malloc(sizeof(long)*1000); /* allocating storage for 1000 long */
あるいは、より良いことには:
int *intPtr = malloc(sizeof(*intPtr)*1000); /* allocating storage for 1000 int */
long *longPtr = malloc(sizeof(*longPtr)*1000); /* allocating storage for 1000 long */
メモリリーク
free
を使用してメモリの割り当てを解除しないと、プログラムでは使用されなくなった再利用不可能なメモリが作成されます。これはメモリリークと呼ばれます 。メモリリークはメモリリソースを浪費し、割り当ての失敗につながります。
論理エラー
すべての割り当ては同じパターンに従わなければなりません:
-
malloc
(またはcalloc
)を使用した割り当て - データを格納するための使用法
-
free
割り当て解除
free
( ダングリングポインター )やmalloc
( ワイルドポインター )の呼び出しの前にメモリーを使用する、 free
2回呼び出す(「ダブルフリー」)など、このパターンを遵守しないと、通常セグメンテーション障害が発生し、結果としてプログラムがクラッシュします。
これらのエラーは一過性であり、デバッグが難しい場合があります。たとえば、解放されたメモリは通常、OSによって即座に再利用されません。したがって、しばらくの間、ポインタが不安定になり、動作するように見えます。
それが動作するシステムでは、 Valgrindは、メモリがリークされた場所と、最初に割り当てられた場所を識別するための貴重なツールです。
スタック変数へのポインタの作成
ポインタを作成しても、指し示す変数の寿命は延びません。例えば:
int* myFunction()
{
int x = 10;
return &x;
}
ここで、 x
は自動保存期間 (一般にスタック割り当てと呼ばれます )を持ちます 。これはスタックに割り当てられているため、 myFunction
が実行されている間だけ有効です。 myFunction
が終了すると、変数x
が破棄されます。この関数は、 x
のアドレスを取得し( &x
を使用)、それを呼び出し側に返し、呼び出し元に存在しない変数へのポインタを残します。この変数にアクセスしようとすると、 未定義の動作が発生します。
ほとんどのコンパイラは、関数が終了した後に実際にスタックフレームをクリアしないので、返されたポインタの逆参照は、しばしば期待されるデータを与えます。ただし、別の関数が呼び出されると、指しているメモリが上書きされ、指し示されているデータが破損しているように見えます。
これを解決するには、返される変数の記憶域をmalloc
し、新しく作成された記憶域へのポインタを返すか、関数を返すのではなく関数に有効なポインタを渡す必要があります。
#include <stdlib.h>
#include <stdio.h>
int *solution1(void)
{
int *x = malloc(sizeof *x);
if (x == NULL)
{
/* Something went wrong */
return NULL;
}
*x = 10;
return x;
}
void solution2(int *x)
{
/* NB: calling this function with an invalid or null pointer
causes undefined behaviour. */
*x = 10;
}
int main(void)
{
{
/* Use solution1() */
int *foo = solution1();
if (foo == NULL)
{
/* Something went wrong */
return 1;
}
printf("The value set by solution1() is %i\n", *foo);
/* Will output: "The value set by solution1() is 10" */
free(foo); /* Tidy up */
}
{
/* Use solution2() */
int bar;
solution2(&bar);
printf("The value set by solution2() is %i\n", bar);
/* Will output: "The value set by solution2() is 10" */
}
return 0;
}
増分/減分と逆参照
あなたが書く場合*p++
によって指されるものインクリメントするp
、あなたは間違っています。
逆参照の前に、ポストインクリメント/デクリメントが実行されます。したがって、この式はポインタのインクリメントしますp
自体をしてによって指摘されたものを返すp
、それを変更することなく、インクリメントする前に。
あなたは書くべき(*p)++
によって指摘されたものを増分するp
。
このルールは、デクリメントを投稿する適用: *p--
ポインタのデクリメントしますp
によって指されているもの、それ自身ではなくp
。
ポインタを間接参照する
int a = 1;
int *a_pointer = &a;
a_pointer
を逆参照し、aの値を変更するには、次の操作を使用します。
*a_pointer = 2;
これは、次のprint文を使用して検証できます。
printf("%d\n", a); /* Prints 2 */
printf("%d\n", *a_pointer); /* Also prints 2 */
しかし、 NULL
ポインタまたは他の方法で無効なポインタを逆参照すると誤解されることになります。この
int *p1, *p2;
p1 = (int *) 0xbad;
p2 = NULL;
*p1 = 42;
*p2 = *p1 + 1;
通常は未定義の動作です。 p1
は、有効なアドレスではない可能性のあるアドレス0xbad
を指しているため、参照解除されないことがあります。何がそこにあるのを誰が知っていますか?オペレーティングシステムのメモリまたは別のプログラムのメモリである可能性があります。このような唯一のタイムコードは、ハードコードされたアドレスに特定の情報を格納する埋め込み型開発で使用されています。 p2
はNULL
であるため、逆参照することはできません。これは無効です。
構造体へのポインタを参照解除する
私たちは次の構造を持っているとしましょう:
struct MY_STRUCT
{
int my_int;
float my_float;
};
私たちはstruct
キーワードを省略するようにMY_STRUCT
を定義することができるので、使用するたびにstruct MY_STRUCT
をタイプする必要はありません。ただしこれはオプションです。
typedef struct MY_STRUCT MY_STRUCT;
この構造体のインスタンスへのポインタがあれば
MY_STRUCT *instance;
この文がファイルスコープに現れると、プログラムが起動するときにinstance
がヌルポインタで初期化されます。このステートメントが関数内にある場合、その値は未定義です。変数は、参照解除される前に、有効なMY_STRUCT
変数を指すように、または動的に割り振られたスペースを指すように初期化する必要があります。例えば:
MY_STRUCT info = { 1, 3.141593F };
MY_STRUCT *instance = &info;
ポインタが有効な場合、2つの異なる表記法の1つを使用してポインタを参照解除してメンバーにアクセスできます。
int a = (*instance).my_int;
float b = instance->my_float;
どちらのメソッドも動作しますが、かっこ、逆参照*
演算子、およびドットの組み合わせではなく、arrow ->
演算子を使用する方が良い方法.
なぜなら、特にネストされた用途では、読みやすく理解しやすいからです。
もう1つの重要な違いを以下に示します。
MY_STRUCT copy = *instance;
copy.my_int = 2;
この場合、 copy
にはinstance
の内容のコピーが含まれinstance
。 copy
my_int
を変更してもinstance
は変更されません。
MY_STRUCT *ref = instance;
ref->my_int = 2;
この場合、 ref
はinstance
への参照です。参照を使用してmy_int
を変更すると、 instance
が変更されinstance
。
構造体自体ではなく、関数のパラメータとして構造体へのポインタを使用するのが一般的です。構造体を関数パラメータとして使用すると、構造体が大きい場合にスタックがオーバーフローする可能性があります。構造体へのポインタを使用すると、ポインタに十分なスタック領域しか使用されませんが、関数が関数に渡された構造体を変更した場合に副作用を引き起こす可能性があります。
関数ポインタ
ポインターを使用して関数をポイントすることもできます。
基本的な機能を取りましょう:
int my_function(int a, int b) { return 2 * a + 3 * b; }
さて、関数の型のポインタを定義しましょう:
int (*my_pointer)(int, int);
作成するには、このテンプレートを使用してください:
return_type_of_func (*my_func_pointer)(type_arg1, type_arg2, ...)
次に、このポインタを関数に割り当てる必要があります。
my_pointer = &my_function;
このポインタを使用して関数を呼び出すことができるようになりました:
/* Calling the pointed function */ int result = (*my_pointer)(4, 2); ... /* Using the function pointer as an argument to another function */ void another_function(int (*another_pointer)(int, int)) { int a = 4; int b = 2; int result = (*another_pointer)(a, b); printf("%d\n", result); }
この構文は、基本型とより自然で一貫しているように見えますが、帰属と逆参照の関数ポインタは&
と*
演算子の使用を必要としません。次のスニペットも同じように有効です。
/* Attribution without the & operator */ my_pointer = my_function; /* Dereferencing without the * operator */ int result = my_pointer(4, 2);
関数ポインタの可読性を高めるために、typedefを使用することができます。
typedef void (*Callback)(int a); void some_function(Callback callback) { int a = 4; callback(a); }
もう1つの可読性のトリックは、Cの標準では、関数プロトタイプのようなものに上記のような引数(ただし変数宣言ではない)の関数ポインタを単純化することができます。したがって、以下は関数定義と宣言に等価的に使用できます。
void some_function(void callback(int))
{
int a = 4;
callback(a);
}
も参照してください
ポインタの初期化
ポインタの初期化は、ワイルドポインタを避けるための良い方法です。初期化は単純であり、変数の初期化と同じです。
#include <stddef.h>
int main()
{
int *p1 = NULL;
char *p2 = NULL;
float *p3 = NULL;
/* NULL is a macro defined in stddef.h, stdio.h, stdlib.h, and string.h */
...
}
ほとんどのオペレーティングシステムでは、 NULL
初期化されたポインタを誤って使用すると、プログラムがすぐにクラッシュし、問題の原因を簡単に特定できます。初期化されていないポインタを使用すると、診断が困難なバグがしばしば発生します。
あぶない:
NULL
ポインタの逆参照の結果は未定義であるため、プログラムが実行されているオペレーティングシステムの自然な動作であっても、 必ずしもクラッシュを引き起こすとは限りません 。コンパイラの最適化は、クラッシュをマスクして、ソースコード内のヌルポインタ逆参照が発生したポイントの前後にクラッシュを発生させたり、ヌルポインタ逆参照を含むコードの一部を予期せずプログラムから削除したりすることがあります。デバッグビルドでは通常これらの動作は行われませんが、言語標準では保証されません。他の予期しないおよび/または望ましくない挙動も許容される。
NULL
は、変数、割り当てられたメモリ、または関数を指していないので、ガード値として使用することは安全です。
あぶない:
通常、 NULL
は(void *)0
として定義されNULL
。しかし、これは割り当てられたメモリアドレスが0x0
ことを意味するものではありません。より明確にするために、NULLポインターのC-faqを参照してください
NULL以外の値を含むようにポインタを初期化することもできます。
int i1;
int main()
{
int *p1 = &i1;
const char *p2 = "A constant string to point to";
float *p3 = malloc(10 * sizeof(float));
}
オペレーターのアドレス(&)
任意のオブジェクト(変数、配列、共用体、構造体、ポインタまたは関数)の場合、単項アドレス演算子を使用してそのオブジェクトのアドレスにアクセスできます。
仮定
int i = 1;
int *p = NULL;
したがって、 p = &i;
変数i
アドレスをポインタp
にコピーします。
それはi
をp
点として表現されます。
printf("%d\n", *p);
i
の値である1を出力します。
ポインタ演算
ここをクリックしてください: ポインタ演算
引数としてのvoid *ポインタを標準関数に返す
void*
は、オブジェクト型へのポインタのすべての型をキャッチします。この使用例は、 malloc
関数を使用しています。これは、以下のように宣言されています。
void* malloc(size_t);
voidへのポインタ戻り値の型は、 malloc
からの戻り値を他のオブジェクト型へのポインタに割り当てることができることを意味します:
int* vector = malloc(10 * sizeof *vector);
一般的には、値を明示的にvoidポインタの中に入れたり外したりしないことが良い方法です。明示的なキャストでは、 stdlib.h
をインクルードするのを忘れてしまった場合、コンパイラはmalloc()
誤った戻り値の型を取るかもしれないが警告しないかもしれないので、 malloc()
特定の場合です。 DRY(自分自身を繰り返さないでください)の原則によく従うためには、voidポインタの正しい動作を使用する場合もあります。上記と以下を比較してください。次のコードには、タイプミスが問題を引き起こす可能性のある不要な追加の場所がいくつか含まれています。
int* vector = (int*)malloc(10 * sizeof int*);
同様に、
void* memcpy(void *restrict target, void const *restrict source, size_t size);
型にかかわらずオブジェクトのアドレスを渡すことができるので、 void *
指定された引数を持つことができます。ここでも、呼び出しはキャスト
unsigned char buffer[sizeof(int)];
int b = 67;
memcpy(buffer, &b, sizeof buffer);
Constポインタ
単一ポインタ
int
へのポインタポインタは異なる整数を指すことができ、
int
ポインタで変更することができます。コードのこのサンプルは、を指すようにBを割り当てるint b
、次に変更のb
への値100
。int b; int* p; p = &b; /* OK */ *p = 100; /* OK */
const int
へのポインタポインタは異なる整数を指すことができますが、
int
値はポインタで変更できません。int b; const int* p; p = &b; /* OK */ *p = 100; /* Compiler Error */
int
へのconst
ポインタポインタは1つの
int
を指すことができますが、int
の値はポインタを通して変更できます。int a, b; int* const p = &b; /* OK as initialisation, no assignment */ *p = 100; /* OK */ p = &a; /* Compiler Error */
const
const int
へのconst
ポインタポインタは1つの
int
を指すだけで、int
をポインタで変更することはできません。int a, b; const int* const p = &b; /* OK as initialisation, no assignment */ p = &a; /* Compiler Error */ *p = 100; /* Compiler Error */
ポインターへのポインター
int
へのポインタへのポインタこのコードは、
p1
のアドレスをdoubleポインタp
代入します(int* p1
(int
を指します)を指します)。次に、
p1
をint a
に指すように変更int a
ます。次に、aの値を100に変更します。void f1(void) { int a, b; int *p1; int **p; p1 = &b; /* OK */ p = &p1; /* OK */ *p = &a; /* OK */ **p = 100; /* OK */ }
const int
へのポインタへのポインタvoid f2(void) { int b; const int *p1; const int **p; p = &p1; /* OK */ *p = &b; /* OK */ **p = 100; /* error: assignment of read-only location ‘**p’ */ }
int
へのconst
ポインタへのポインタvoid f3(void) { int b; int *p1; int * const *p; p = &p1; /* OK */ *p = &b; /* error: assignment of read-only location ‘*p’ */ **p = 100; /* OK */ }
int
へのポインタへのconst
ポインタvoid f4(void) { int b; int *p1; int ** const p = &p1; /* OK as initialisation, not assignment */ p = &p1; /* error: assignment of read-only variable ‘p’ */ *p = &b; /* OK */ **p = 100; /* OK */ }
const int
へのconst
ポインタへのポインタvoid f5(void) { int b; const int *p1; const int * const *p; p = &p1; /* OK */ *p = &b; /* error: assignment of read-only location ‘*p’ */ **p = 100; /* error: assignment of read-only location ‘**p’ */ }
const
const int
へのポインタへのconst int
ポインタvoid f6(void) { int b; const int *p1; const int ** const p = &p1; /* OK as initialisation, not assignment */ p = &p1; /* error: assignment of read-only variable ‘p’ */ *p = &b; /* OK */ **p = 100; /* error: assignment of read-only location ‘**p’ */ }
int
へのconst
ポインタへのconst
ポインタvoid f7(void) { int b; int *p1; int * const * const p = &p1; /* OK as initialisation, not assignment */ p = &p1; /* error: assignment of read-only variable ‘p’ */ *p = &b; /* error: assignment of read-only location ‘*p’ */ **p = 100; /* OK */ }
同じアスタリスク、異なる意味
前提
CとC ++のポインタ構文を取り巻く最も混乱しやすいことは、ポインタシンボルであるアスタリスク( *
)が変数とともに使用されるときに実際には2つの異なる意味が適用されるということです。
例
まず、 *
を使用してポインタ変数を宣言します。
int i = 5;
/* 'p' is a pointer to an integer, initialized as NULL */
int *p = NULL;
/* '&i' evaluates into address of 'i', which then assigned to 'p' */
p = &i;
/* 'p' is now holding the address of 'i' */
宣言していないとき(または乗算していないとき)、 *
はポインタ変数の参照を解除するために使われます:
*p = 123;
/* 'p' was pointing to 'i', so this changes value of 'i' to 123 */
既存のポインタ変数に他の変数のアドレスを保持させたい場合、 *
は使用しませんが 、次のようにします:
p = &another_variable;
Cプログラミングの初心者の間で共通の混乱は、ポインタ変数を同時に宣言して初期化するときに発生します。
int *p = &i;
int i = 5;
そしてint i; i = 5;
同じ結果を与え、それらのうちのいくつかはint *p = &i;
とint *p; *p = &i;
同じ結果を与える。事実は、いいえ、 int *p; *p = &i;
初期化されていないポインタを尊重しようとします。このポインタはUBになります。ポインタを宣言したりデリファレンスしていないときは、 *
使用しないでください。
結論
アスタリスク( *
)は、ポインタが使用されている場所に応じて、C内でポインタとの関係で2つの異なる意味を持ちます。 変数宣言内で使用する場合、等辺の右辺の値は、メモリ内のアドレスに対するポインタ値でなければなりません。既に宣言されている変数と一緒に使用すると、アスタリスクはポインター値を逆参照し 、ポインター値をメモリに配置し、そこに格納されている値を代入または取得できるようにします。
取り除く
ポインターを扱うときは、あなたのPとQを気にすることが重要です。あなたがアスタリスクを使用しているとき、そしてあなたがそこでそれを使用するとき、それが何を意味するのか注意してください。この小さなディテールを見落とすと、実際に対処したくないバグや未定義の動作につながる可能性があります。
ポインターへのポインター
Cでは、ポインタは別のポインタを参照することができます。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int A = 42;
int* pA = &A;
int** ppA = &pA;
int*** pppA = &ppA;
printf("%d", ***pppA); /* prints 42 */
return EXIT_SUCCESS;
}
しかし、参照と参照は直接許可されません。
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int A = 42;
int* pA = &A;
int** ppA = &&A; /* Compilation error here! */
int*** pppA = &&&A; /* Compilation error here! */
...
前書き
ポインタは変数の型と名前の間にアスタリスク( *
)が置かれていることを除いて、他の変数と同様に宣言されます。
int *pointer; /* inside a function, pointer is uninitialized and doesn't point to any valid object yet */
同じ型の2つのポインタ変数を宣言するには、同じ宣言で、各識別子の前にアスタリスク記号を使用します。例えば、
int *iptr1, *iptr2;
int *iptr3, iptr4; /* iptr3 is a pointer variable, whereas iptr4 is misnamed and is an int */
アンパサンド( &
)で示されるアドレス演算子または参照演算子は、適切な型のポインタに配置できる変数のアドレスを与えます。
int value = 1;
pointer = &value;
アスタリスク( *
)で示される間接参照または逆参照演算子は、ポインタが指すオブジェクトの内容を取得します。
printf("Value of pointed to integer: %d\n", *pointer);
/* Value of pointed to integer: 1 */
ポインタが構造体または共用体型を指している場合、それを逆参照して、 ->
演算子を使用してそのメンバーに直接アクセスできます。
SomeStruct *s = &someObject;
s->someMember = 5; /* Equivalent to (*s).someMember = 5 */
Cでは、ポインタは再割り当て可能な別個の値型であり、それ以外の場合はそれ自身の変数として扱われます。たとえば、次の例は、ポインタ(変数)自体の値を出力します。
printf("Value of the pointer itself: %p\n", (void *)pointer);
/* Value of the pointer itself: 0x7ffcd41b06e4 */
/* This address will be different each time the program is executed */
ポインタは可変変数であるため、有効なオブジェクトを指していない可能性があります。これは、nullに設定されているか
pointer = 0; /* or alternatively */
pointer = NULL;
または単に有効なアドレスではない任意のビットパターンを含むことにより、後者は非常に悪い状況です。なぜなら、ポインタが逆参照される前にテストすることはできないからです。ポインタがnullの場合のテストしかありません。
if (!pointer) exit(EXIT_FAILURE);
ポインタは、 有効なオブジェクトを指している場合にのみ逆参照することができます。そうでない場合、ポインタは未定義です。現代の実装の多くは、 セグメンテーションフォールトや実行を終了させるなどのエラーを発生させて助けてくれるかもしれませんが、他のものはあなたのプログラムを無効な状態にしてしまうかもしれません。
逆参照演算子によって戻される値は、元の変数への変更可能な別名であるため、元の変数を変更して変更することができます。
*pointer += 1;
printf("Value of pointed to variable after change: %d\n", *pointer);
/* Value of pointed to variable after change: 2 */
ポインタも再割り当て可能です。つまり、オブジェクトを指すポインターを使用して、後で同じタイプの別のオブジェクトを指すことができます。
int value2 = 10;
pointer = &value2;
printf("Value from pointer: %d\n", *pointer);
/* Value from pointer: 10 */
他の変数と同様に、ポインタには特定の型があります。 short int
アドレスはlong int
へのポインタに代入することはできません。このような振る舞いは型打と呼ばれ、Cでは禁止されていますが、いくつかの例外があります。
ポインターは特定の型でなければなりませんが、ポインターの種類ごとに割り当てられるメモリーは、指し示される型のサイズではなく、アドレスを格納するために環境によって使用されるメモリーと同じです。
#include <stdio.h>
int main(void) {
printf("Size of int pointer: %zu\n", sizeof (int*)); /* size 4 bytes */
printf("Size of int variable: %zu\n", sizeof (int)); /* size 4 bytes */
printf("Size of char pointer: %zu\n", sizeof (char*)); /* size 4 bytes */
printf("Size of char variable: %zu\n", sizeof (char)); /* size 1 bytes */
printf("Size of short pointer: %zu\n", sizeof (short*)); /* size 4 bytes */
printf("Size of short variable: %zu\n", sizeof (short)); /* size 2 bytes */
return 0;
}
(注:C99またはC11標準をサポートしていないMicrosoft Visual Studioを使用している場合は、上記サンプルで%zu
代わりに%zu
%Iu
1を使用する必要があります)。
上記の結果は環境によって異なる場合がありますが、すべての環境が異なるタイプのポインタに対して同じサイズを示すことに注意してください。
カーディフ大学の情報に基づいた抽出
ポインタと配列
ポインタと配列はCで密接に接続されています。Cの配列は常にメモリ内の連続した位置に保持されます。ポインタの算術演算は常に、指し示されている項目のサイズによってスケーリングされます。したがって、3つのdouble型の配列と基底へのポインタを持つ場合、 *ptr
は最初のdoubleを指します。 *(ptr + 1)
は2番目の*(ptr + 2)
は3番目のdoubleです。より便利な表記法は、配列記法[]
を使うことです。
double point[3] = {0.0, 1.0, 2.0};
double *ptr = point;
/* prints x 0.0, y 1.0 z 2.0 */
printf("x %f y %f z %f\n", ptr[0], ptr[1], ptr[2]);
したがって、本質的にptrと配列名は互換性があります。このルールは、配列がサブルーチンに渡されるときにポインタに減衰することも意味します。
double point[3] = {0.0, 1.0, 2.0};
printf("length of point is %s\n", length(point));
/* get the distance of a 3D point from the origin */
double length(double *pt)
{
return sqrt(pt[0] * pt[0] + pt[1] * pt[1] + pt[2] * pt[2])
}
ポインタは、配列内の任意の要素、または最後の要素を超えた要素を指すことがあります。ただし、配列の前の要素を含む他の値へのポインタを設定するのはエラーです。 (なぜなら、セグメント化されたアーキテクチャでは、最初の要素の前のアドレスがセグメント境界を越える可能性があるため、コンパイラは最後の要素に1を加えたものが発生しないことを保証します)。
脚注1:Microsoftの書式情報は、 printf()
と書式指定の構文で見つけることができます 。
voidポインターによる多態性の振る舞い
qsort()
標準ライブラリ関数は、ボイドポインタを使って単一の関数を多種多様な型で動作させる方法の良い例です。
void qsort (
void *base, /* Array to be sorted */
size_t num, /* Number of elements in array */
size_t size, /* Size in bytes of each element */
int (*compar)(const void *, const void *)); /* Comparison function for two elements */
ソートされる配列はvoidポインタとして渡されるため、任意のタイプの要素の配列を操作できます。次の2つの引数は、 qsort()
配列で期待される要素の数と各要素の大きさ(バイト数)を示します。
最後の引数は、2つのvoidポインタをとる比較関数への関数ポインタです。呼び出し側がこの関数を提供するようにすることで、 qsort()
はあらゆる型の要素を効果的にソートできます。
浮動小数点数を比較するための比較関数の例を次に示します。 qsort()
渡される比較関数は、この型シグネチャを持つ必要があることに注意してください。ポリモーフィックにする方法は、voidポインタの引数を、比較したい要素の型のポインタにキャストすることです。
int compare_floats(const void *a, const void *b)
{
float fa = *((float *)a);
float fb = *((float *)b);
if (fa < fb)
return -1;
if (fa > fb)
return 1;
return 0;
}
私たちはqsortがこの関数を使って浮動小数点数を比較することを知っているので、逆参照する前にvoidポインタの引数をfloatポインタにキャストします。
さて、長さ "len"の配列 "配列"の多形関数qsortの使い方はとても簡単です:
qsort(array, len, sizeof(array[0]), compare_floats);