C Language
メモリ管理
サーチ…
前書き
動的に割り当てられたメモリを管理するために、標準Cライブラリは関数malloc()
、 calloc()
、 realloc()
、 realloc()
をfree()
ます。 C99以降では、 aligned_alloc()
もあります。システムによっては、 alloca()
も用意されています。
構文
- void * aligned_alloc(size_t alignment、size_t size); / * C11以降のみ* /
- void * calloc(size_t nelements、size_t size);
- void free(void * ptr);
- void * malloc(size_t size);
- void * realloc(void * ptr、size_t size);
- void * alloca(size_t size); / *標準ではなく、ポータブルではない、危険なalloca.hから。 * /
パラメーター
名 | 説明 |
---|---|
サイズ( malloc 、 realloc 、およびaligned_alloc ) | メモリの合計サイズ(バイト単位)。 aligned_alloc 、サイズは整列の整数倍でなければなりません。 |
サイズ( calloc ) | 各要素のサイズ |
ニレメンツ | 要素の数 |
ptr | 以前にmalloc 、 calloc 、 realloc またはaligned_alloc によって返された割り当て済みメモリへのポインタ |
アライメント | 割り当てられたメモリの配置 |
備考
aligned_alloc()
は、C11以降でのみ定義されています。
POSIXに基づくシステムなどのシステムは、整列したメモリ( posix_memalign()
)を割り当てる他の方法を提供し、その他のメモリ管理オプション( mmap()
)も備えています。
メモリーの解放
free()を呼び出して動的に割り当てられたメモリを解放することは可能です。
int *p = malloc(10 * sizeof *p); /* allocation of memory */
if (p == NULL)
{
perror("malloc failed");
return -1;
}
free(p); /* release of memory */
/* note that after free(p), even using the *value* of the pointer p
has undefined behavior, until a new value is stored into it. */
/* reusing/re-purposing the pointer itself */
int i = 42;
p = &i; /* This is valid, has defined behaviour */
p
が指すメモリは、 free()
呼び出し後に(libcの実装か基礎となるOSのどちらかによって)再利用されるため、解放されたメモリブロックにp
アクセスすると未定義の動作になります。解放されたメモリ要素を参照するポインタは 、通常、 ダングリングポインタと呼ばれ、セキュリティ上のリスクがあります。さらに、C標準では、ぶら下がっているポインタの値にアクセスすることさえも未定義の振舞いがあると述べています。上記のように、ポインタp
自体を再利用することができます。
あなただけ呼び出すことができることに注意してくださいfree()
直接から返されたポインタにmalloc()
calloc()
realloc()
とaligned_alloc()
関数、またはドキュメントがメモリがそのよう(機能割り当てられているかを示しますstrdup ()
ようなものは注目に値する例です)。ポインタを解放すると、
- 変数に
&
演算子を使用して得られる - 割り当てられたブロックの途中で、
禁止されています。このようなエラーは通常、コンパイラによって診断されることはありませんが、プログラムの実行は未定義の状態になります。
このような定義されていない動作を防ぐには、2つの共通の戦略があります。
最初と望ましいものは単純です - p
が不要になったときにp
自体が存在しなくなりました。例えば:
if (something_is_needed())
{
int *p = malloc(10 * sizeof *p);
if (p == NULL)
{
perror("malloc failed");
return -1;
}
/* do whatever is needed with p */
free(p);
}
包含ブロック(つまり}
)の終わりの直前でfree()
呼び出すことによって、 p
自体は存在しなくなります。コンパイラは、その後にp
を使用しようとすると、コンパイルエラーが発生します。
2番目の方法は、ポインターが指しているメモリーを解放した後にポインター自体を無効にすることです。
free(p);
p = NULL; // you may also use 0 instead of NULL
このアプローチの議論:
多くのプラットフォームで、ヌルポインタの逆参照を試みると、即時クラッシュが発生します。ここでは、少なくとも解放された後に使用された変数を指すスタックトレースを取得します。
ポインタを
NULL
設定しないと、ポインタがぶら下がってしまいます。プログラムがクラッシュする可能性は非常に高いですが、後でポインタが指すメモリが黙って壊れてしまうためです。このようなバグは、最初の問題とは完全に無関係なコールスタックにつながる可能性があるため、トレースすることは困難です。したがって、このアプローチはフェイル・ファーストのコンセプトに従います。
ヌルポインタを解放することは安全です。 Cの標準では 、
free(NULL)
は無効であることが指定されています。free関数は、ptrが指し示すスペースの割り当てを解除します。つまり、後で割り当てできるようになります。 ptrがヌルポインタの場合、アクションは発生しません。それ以外の場合は、引数がポインタと一致しない場合は、以前から返された
calloc
、malloc
、またはrealloc
機能、またはスペースが呼び出しによって割り当て解除された場合はfree
またはrealloc
動作は未定義です。
- 時には、最初のアプローチを使用することはできません(たとえば、メモリはある関数に割り当てられ、まったく異なる関数では後で割り当て解除されます)
メモリの割り当て
標準配分
Cの動的メモリ割り当て関数は、 <stdlib.h>
ヘッダで定義されています。オブジェクトのメモリ空間を動的に割り当てる場合は、次のコードを使用できます。
int *p = malloc(10 * sizeof *p);
if (p == NULL)
{
perror("malloc() failed");
return -1;
}
これは、10バイトの数を計算int
、その後、多くのバイトをすることを要求し、sがメモリに占有をmalloc
とは、結果を代入し(すなわち、単に使用して作成されたメモリチャンクの開始アドレスmalloc
)ポインタという名前にp
。
sizeof
の結果は実装定義されているので( sizeof
が常に1
になるようにchar
、 signed char
およびunsigned char
である文字型を除く)、 sizeof
の結果を求めるのにsizeof
を使用することをおsizeof
します。
malloc
はリクエストを処理できない可能性があるため、nullポインタを返す可能性があります。後でヌルポインタの逆参照を防止するために、これをチェックすることが重要です。
malloc()
を使用して動的に割り当てられたメモリは、 realloc()
を使用してサイズ変更するか、不要になったときにfree()
を使用して解放することがfree()
ます。
代わりに、 int array[10];
宣言しint array[10];
同じ量のメモリが割り当てられます。しかし、キーワードstatic
持たない関数の中で宣言されていると、宣言された関数とそれが呼び出す関数内でのみ使用できます(配列はスタックに割り当てられ、スペースは再利用のために解放されるためです)。関数が戻ります)。あるいは、関数内でstatic
定義されてstatic
場合、または関数の外で定義されている場合、その存続期間はプログラムの存続期間です。ポインタも関数から返すことができますが、Cの関数は配列を返すことはできません。
ゼロメモリ
malloc
返されたメモリは妥当な値に初期化されていない可能性があり、 memset
使用してメモリをゼロにするか、適切な値をすぐにコピーするように注意する必要があります。あるいは、 calloc
は、すべてのビットが0
初期化される、望ましいサイズのブロックを返します。これは、浮動小数点ゼロまたはヌルポインタ定数の表現と同じである必要はありません。
int *p = calloc(10, sizeof *p);
if (p == NULL)
{
perror("calloc() failed");
return -1;
}
calloc
に関する注意:ほとんどの(一般的に使用される)実装は、パフォーマンスのためにcalloc()
を最適化します。したがって、ネットエフェクトは同じですが、 malloc()
、 memset()
呼び出すよりも速くなります。
メモリの整列
C11は、指定された配置でスペースを割り当てる新しい関数aligned_alloc()
を導入しました。割り当てられるメモリが、 malloc()
またはcalloc()
で満たすことができない境界で整列する必要がある場合に使用できます。 malloc()
およびcalloc()
関数は、 任意のオブジェクト型に対して適切に整列されたメモリを割り当てます(つまり、整列はalignof(max_align_t)
)。しかし、 aligned_alloc()
より大きな整列を要求できます。
/* Allocates 1024 bytes with 256 bytes alignment. */
char *ptr = aligned_alloc(256, 1024);
if (ptr) {
perror("aligned_alloc()");
return -1;
}
free(ptr);
C11標準は、2つの制約を課す:1)要求されたサイズ (第2引数)がアライメント (最初の引数)の整数倍でなければならず、2) アライメントの値は実装によってサポートされる有効な位置合わせであるべきです。いずれかを満たさないと、 未定義の動作が発生します。
メモリの再割り当て
メモリを割り当てた後、ポインタの記憶領域を拡大または縮小する必要があるかもしれません。 void *realloc(void *ptr, size_t size)
関数は解放古いオブジェクトは、によって指さptr
により指定されたサイズを持っているオブジェクトへのポインタを返すsize
。 ptr
は、以前に割り当てられたmalloc
、 calloc
またはrealloc
(またはヌルポインタ)で割り当てられたメモリブロックへのポインタです。元のメモリの最大限の内容が保存されます。新しいサイズが大きい場合、古いサイズを超える追加のメモリは初期化されません。新しいサイズが短い場合、縮小部分の内容は失われます。 ptr
がNULLの場合、新しいブロックが割り当てられ、その関数へのポインタが返されます。
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
int *p = malloc(10 * sizeof *p);
if (NULL == p)
{
perror("malloc() failed");
return EXIT_FAILURE;
}
p[0] = 42;
p[9] = 15;
/* Reallocate array to a larger size, storing the result into a
* temporary pointer in case realloc() fails. */
{
int *temporary = realloc(p, 1000000 * sizeof *temporary);
/* realloc() failed, the original allocation was not free'd yet. */
if (NULL == temporary)
{
perror("realloc() failed");
free(p); /* Clean up. */
return EXIT_FAILURE;
}
p = temporary;
}
/* From here on, array can be used with the new size it was
* realloc'ed to, until it is free'd. */
/* The values of p[0] to p[9] are preserved, so this will print:
42 15
*/
printf("%d %d\n", p[0], p[9]);
free(p);
return EXIT_SUCCESS;
}
再割り当てされたオブジェクトは、 *p
と同じアドレスを持っていてもいなくてもかまいません。したがって、呼び出しが成功した場合は、新しいアドレスを含むrealloc
からの戻り値を取得することが重要です。
realloc
の戻り値を元のp
代わりにtemporary
に割り当てるようにしてください。 realloc
は、何らかの障害が発生した場合にnullを返し、ポインタを上書きします。これにより、データが失われ、メモリリークが発生します。
可変サイズの多次元配列
C99以降、Cには可変長配列VLAがあり、初期化時にのみ認識される境界を持つモデル配列となります。大きすぎるVLA(スタックを壊す可能性があります)を割り当てないように注意する必要がありますが、VLAへのポインタを使用してsizeof
式でポインタを使用すると問題ありません。
double sumAll(size_t n, size_t m, double A[n][m]) {
double ret = 0.0;
for (size_t i = 0; i < n; ++i)
for (size_t j = 0; j < m; ++j)
ret += A[i][j]
return ret;
}
int main(int argc, char *argv[argc+1]) {
size_t n = argc*10;
size_t m = argc*8;
double (*matrix)[m] = malloc(sizeof(double[n][m]));
// initialize matrix somehow
double res = sumAll(n, m, matrix);
printf("result is %g\n", res);
free(matrix);
}
ここでmatrix
はdouble[m]
型の要素へのポインタであり、 double[n][m]
を持つsizeof
式はn
要素のためのスペースを含むことを保証する。
このスペースはすべて連続して割り当てられ、したがってfree
への1回の呼び出しで割り当てを解除することができます。
言語でのVLAの存在は、関数ヘッダー内の配列とポインターの可能な宣言にも影響します。さて、一般的な整数式は配列パラメータの[]
内で許されます。どちらの関数でも、 []
内の式は、パラメータリストで前に宣言したパラメータを使用します。 sumAll
の場合、これらはユーザコードが行列に対して期待する長さです。 Cのすべての配列関数のパラメータに関して、最も内側の次元はポインタ型に書き直されるので、これは宣言と同じです
double sumAll(size_t n, size_t m, double (*A)[m]);
つまり、 n
は実際には関数インタフェースの一部ではありませんが、情報は文書化に役立ち、境界外のアクセスについて警告するために境界チェックコンパイラによっても使用できます。
同様に、 main
場合、式argc+1
は、 argv
引数に対してC標準が規定する最小の長さです。
公式にVLAサポートはC11ではオプションですが、C11を実装していないコンパイラがないことはわかっています。必要なら__STDC_NO_VLA__
マクロでテストすることができます。
realloc(ptr、0)はfree(ptr)と等価ではありません。
realloc
は概念的には、他のポインタのmalloc + memcpy + free
と同じです。
要求されたスペースのサイズがゼロの場合、 realloc
の動作は実装定義です。これは、値0
size
パラメータを受け取るすべてのメモリ割り当て関数で同様です。そのような関数は、実際にはnull以外のポインタを返すかもしれませんが、逆参照されることはありません。
したがって、 realloc(ptr,0)
はfree(ptr)
と等価ではありません。それはかもしれない
- 「怠惰な」実装で、ちょうど
ptr
返す -
free(ptr)
、ダミー要素を割り当てて -
free(ptr)
して0
を返す - 失敗の場合は
0
を返し、それ以外は何もしません。
特に、後者の2つのケースはアプリケーションコードによって区別できません。
これは、 realloc(ptr,0)
が実際にメモリを解放/解放しない可能性があることを意味します。したがって、 free
代わりに使用するべきではありません。
ユーザー定義のメモリ管理
malloc()
は、メモリのページを取得するために、基本的なオペレーティングシステムの関数を呼び出します。しかし、関数には何も特別なものはなく、大きな静的配列を宣言してそこから割り付けることでストレートCで実装することができます(実際には8バイトに整列するのが適切です)。
簡単なスキームを実装するために、呼び出しから返されるポインタの直前にメモリの領域に制御ブロックが格納されます。つまり、 free()
返されたポインタから減算すると、典型的にはブロックサイズに加え、それがフリーリストに戻されることを可能にするいくつかの情報である制御情報、オフ読み取ることによって実施されてもよい-未割り当てのブロックのリンクリストを。
ユーザーが割り当てを要求すると、要求された量と同じかそれ以上のサイズのブロックが見つかるまで空きリストが検索され、必要であれば分割されます。これは、ユーザーが予測不能なサイズと予測できない間隔で多くの割り当てを継続して解放している場合にメモリの断片化につながります(すべての実際のプログラムがそのように動作するわけではありません。
/* typical control block */
struct block
{
size_t size; /* size of block */
struct block *next; /* next block in free list */
struct block *prev; /* back pointer to previous block in memory */
void *padding; /* need 16 bytes to make multiple of 8 */
}
static struct block arena[10000]; /* allocate from here */
static struct block *firstfree;
多くのプログラムでは、同じサイズの小さなオブジェクトを多数割り当てる必要があります。これは実装が非常に簡単です。次のポインタでブロックを使用するだけです。したがって、32バイトのブロックが必要な場合:
union block
{
union block * next;
unsigned char payload[32];
}
static union block arena[100];
static union block * head;
void init(void)
{
int i;
for (i = 0; i < 100 - 1; i++)
arena[i].next = &arena[i + 1];
arena[i].next = 0; /* last one, null */
head = &block[0];
}
void *block_alloc()
{
void *answer = head;
if (answer)
head = head->next;
return answer;
}
void block_free(void *ptr)
{
union block *block = ptr;
block->next = head;
head - block;
}
この方式は非常に高速かつ効率的であり、一定の明瞭さを失うことなく一般化することができます。
alloca:スタックにメモリを割り当てる
警告: alloca
は、完全性のためにここで言及されています。これは完全にポータブルではなく(一般的な標準ではカバーされていません)、潜在的に危険な機能が多数あり、認識できないほど安全ではありません。最新のCコードは、 可変長配列 (VLA)で置き換えるべきです。
#include <alloca.h>
// glibc version of stdlib.h include alloca.h by default
void foo(int size) {
char *data = alloca(size);
/*
function body;
*/
// data is automatically freed
}
呼び出し元のスタックフレームにメモリを割り当てます。呼び出し元関数が終了すると、返されたポインタによって参照される領域は自動的に解放されます。
この関数は自動メモリ管理に便利ですが、大きな割り当てを要求するとスタックのオーバーフローが発生する可能性があり、 alloca
割り当てられたメモリをfree
で使用できないことに注意してください(スタックオーバーフローの問題が発生する可能性があります)。
これらの理由から、ループ内でも再帰関数内でもalloca
を使用することは推奨されません。
また、関数の戻り時にメモリがfree
れるため、関数の結果としてポインタを返すことはできません( 動作は未定義です )。
概要
-
malloc
と同じものを呼び出す - 関数戻り時に自動的にフリーズされる
-
free
realloc
関数と互換性がありません( 未定義の動作 ) - ポインタを関数の結果として返すことはできません( 未定義の動作 )
- 割り振りサイズはスタックスペースによって制限されます。これは(ほとんどのマシンでは)
malloc()
によって使用可能なヒープスペースよりもずっと小さくなります。 -
alloca()
とVLA(可変長配列)を1つの関数で使用しないようにする -
alloca()
はmalloc()
らと同じくらい移植性がありません
勧告
- 新しいコードでは
alloca()
を使わないでください
現代の代替。
void foo(int size) {
char data[size];
/*
function body;
*/
// data is automatically freed
}
これはalloca()
が動作するところで動作し、 alloca()
が(例えばループの内部でalloca()
そうでないところで動作します。 __STDC_NO_VLA__
定義していないC99実装またはC11実装のいずれかを想定しています。