サーチ…


前書き

動的に割り当てられたメモリを管理するために、標準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から。 * /

パラメーター

説明
サイズ( mallocrealloc 、およびaligned_allocメモリの合計サイズ(バイト単位)。 aligned_alloc 、サイズは整列の整数倍でなければなりません。
サイズ( calloc各要素のサイズ
ニレメンツ要素の数
ptr 以前にmalloccallocreallocまたはaligned_allocによって返された割り当て済みメモリへのポインタ
アライメント割り当てられたメモリの配置

備考

C11

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がヌルポインタの場合、アクションは発生しません。それ以外の場合は、引数がポインタと一致しない場合は、以前から返されたcallocmalloc 、または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になるようにcharsigned 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

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により指定されたサイズを持っているオブジェクトへのポインタを返すsizeptrは、以前に割り当てられたmalloccallocまたは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

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

ここでmatrixdouble[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()を使わないでください
C99

現代の代替。

void foo(int size) {
    char data[size];
    /*
      function body;
    */
    // data is automatically freed
}

これはalloca()が動作するところで動作し、 alloca()が(例えばループの内部でalloca()そうでないところで動作します。 __STDC_NO_VLA__定義していないC99実装またはC11実装のいずれかを想定しています。



Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow