C言語の動的メモリ割り当てとは、C標準ライブラリの関数群(主に、、、、および)を介して、Cプログラミング言語で動的メモリ割り当てのための手動メモリ管理を実行することを指します。[ 1 ] [ 2 ] [ 3 ]mallocrealloccallocaligned_allocfree
C ++プログラミング言語にはこれらの関数が含まれていますが、 new演算子とdelete演算子は同様の機能を提供し、その言語の開発者によって推奨されています。[ 4 ]ただし、ガベージ コレクション コードやパフォーマンスに敏感なコードなど、 / の使用が適用できない状況がいくつかあり、高レベルの演算子の代わりに、との組み合わせが必要になる場合があります。newdeletemallocnewnew
mallocで使用される実際のメモリ割り当てメカニズムには、さまざまな実装が存在します。それらのパフォーマンスは、実行時間と必要なメモリの両面で異なります。
C 言語は、メモリを静的、自動、または動的に管理します。静的期間変数は、通常プログラムの実行可能コードとともにメインメモリに割り当てられ、プログラムの実行期間中保持されます。自動期間変数はスタックに割り当てられ、関数が呼び出されて戻ると生成および消滅します。静的期間変数と自動期間変数の場合、割り当てサイズはコンパイル時に定数である必要があります (可変長自動配列の場合を除く[ 5 ] )。必要なサイズが実行時までわからない場合(たとえば、任意のサイズのデータがユーザーまたはディスク ファイルから読み込まれる場合)、固定サイズのデータ オブジェクトを使用することは適切ではありません。
割り当てられたメモリの有効期間も問題となる場合があります。静的メモリも自動メモリも、あらゆる状況に対応できるわけではありません。自動割り当てデータは複数の関数呼び出しをまたいで保持することはできませんが、静的データは必要かどうかに関わらずプログラムの実行期間中保持されます。多くの場合、プログラマは割り当てられたメモリの有効期間をより柔軟に管理する必要があります。
これらの制限は、動的メモリ割り当てを使用することで回避できます。動的メモリ割り当てでは、メモリはより明示的に(ただしより柔軟に)管理され、通常はヒープ(空き領域)から割り当てられます。ヒープは、この目的のために構造化されたメモリ領域です。C言語では、ライブラリ関数をmalloc使用してヒープ上にメモリブロックを割り当てます。プログラムは、戻り値を返すポインタを介してこのメモリブロックにアクセスします。メモリmallocが不要になった場合は、ポインタがに渡され、freeメモリが解放されて他の用途に使用できるようになります。
C の元の説明では、callocと はcfree標準ライブラリに含まれているが、は含まれていないと示されていました。Unix用mallocのストレージマネージャの単純なモデル実装のコードが、ユーザーインターフェイス関数としてと を使用し、システムコールを使用してオペレーティングシステムからメモリを要求する形で提供されました。[ 6 ]第 6 版 Unix ドキュメントでは、と が低レベルのメモリ割り当て関数として示されています。[ 7 ]とルーチンの最新の形式は、第 7 版 Unix マニュアルで完全に説明されています。[ 8 ] [ 9 ]allocfreesbrkallocfreemallocfree
一部のプラットフォームでは、ライブラリ関数や組み込み関数の呼び出しによって、ヒープではなくCスタックから実行時に動的にメモリを割り当てることができます(例:alloca()[ 10 ])。このメモリは、呼び出し元の関数が終了すると自動的に解放されます。
C言語の動的メモリ割り当て関数は<stdlib.h>ヘッダーファイル(<cstdlib>C++ではヘッダーファイル)で定義されています。[ 1 ]
malloc()との違いcalloc()malloc()は引数を1つ(割り当てるメモリ量をバイト単位で指定)取りますが、 はcalloc()引数を2つ(要素数と各要素のサイズ)取ります。malloc()はメモリを割り当てるだけですが、calloc()は割り当てた領域のバイトをゼロに設定して割り当てます。[ 11 ]C言語では、自動スコープを持つ10個の整数の配列を作成するのは簡単です。
int a [ 10 ];ただし、配列のサイズはコンパイル時に固定されます。可変長配列を使用せずに同様の配列を動的に割り当てたい場合は、すべてのC11実装でサポートされているとは限らないため、 を使用して配列を割り当てることができますmalloc。 はvoid ポインタ(未知のデータ型の領域へのポインタであることを示す) を返しますが、安全のためにキャストすることができます。
int * a = ( int * ) malloc ( 10 * sizeof ( int ));これは、10 個の整数がメモリ内で占めるバイト数を計算し、そのバイト数を要求してmalloc、結果をという名前のポインタaに割り当てます(C 構文では、ポインタと配列は状況によっては互換的に使用できます)。
mallocリクエストを処理できない場合、ヌルポインタが返される可能性があるため、これをチェックすることは良いプログラミング習慣です。
int * a = ( int * ) malloc ( 10 * sizeof ( int )); if ( ! a ) { fprintf ( stderr , "malloc failed \n " ); return -1 ; }プログラムが動的配列を必要としなくなった場合、最終的には、free占有しているメモリを空き領域に返却する関数を呼び出す必要があります。
無料(a )によって確保されたメモリは初期化されmallocておらず、以前に使用されて破棄されたデータの残骸である不要なデータが含まれている可能性があります。 による割り当て後、配列の要素は初期化されていない変数です。 コマンドは、既にクリアされた割り当てを返します。malloccalloc
int * a = ( int * ) calloc ( 10 , sizeof ( int ));realloc を使用すると、ポインタが指すメモリの量を変更できます。たとえば、サイズの配列として機能するポインタがある場合そして、それをサイズの配列に変更したいrealloc を使用できます。
int * a = ( int * ) malloc ( 2 * sizeof ( int )); a [ 0 ] = 1 ; a [ 1 ] = 2 ; a = ( int * ) realloc ( a , 3 * sizeof ( int )); a [ 2 ] = 3 ;realloc はブロックのベースアドレスを変更したと想定する必要があることに注意してください (つまり、元のブロックのサイズを拡張できず、別の場所に新しいより大きなブロックを割り当てて、古い内容をそこにコピーした場合)。したがって、元のブロック内のアドレスへのポインタも無効になります。
前述のとおり、void ポインタ( )mallocを返します。これは、データ型が不明な領域へのポインタであることを示しています。C++ では強力な型システムのためキャストが必要ですが、C ではそうではありません。このポインタを特定の型に「キャスト」することができます (型変換を参照)。void*
// キャストなしint * ptr1 = malloc ( 10 * sizeof ( * ptr ));// キャストを使用する場合int * ptr2 = ( int * ) malloc ( 10 * sizeof ( * ptr ));このようなキャストを実行することには、利点と欠点があります。キャストを含めることで、C と C++ の相互運用性が向上し、C コードを C++ としてコンパイルまたは使用できるようになります。さらに、キャストにより、元々 を返していた1989 年以前のバージョンも使用できるようになります。[ 12 ]また、キャストは、特にポインタが呼び出しから遠く離れた場所で宣言されている場合、宛先ポインタの型が変更された場合に、型サイズの不整合を開発者が特定するのに役立ちます(ただし、最新のコンパイラと静的アナライザーは、キャストを必要とせずにそのような動作について警告することができます[ 13 ] )。mallocchar*malloc()
しかし、C 標準ではキャストは不要であり、キャストを追加すると<stdlib.h>、関数プロトタイプがmalloc見つかるヘッダーのインクルードの失敗を隠蔽する可能性があります。[ 12 ] [ 14 ]のプロトタイプがない場合malloc、C90 標準では、C コンパイラがmallocを返すことを想定する必要がありますint。キャストがない場合、C90 ではこの整数がポインタに割り当てられたときに診断を要求しますが、キャストを使用するとこの診断は生成されず、バグが隠蔽されます。特定のアーキテクチャとデータ モデル (64 ビット システム上の LP64 など、longポインタが 64 ビットで がint32 ビットの場合) では、暗黙的に宣言された は 32 ビット値を返し、実際に定義された関数は 64 ビット値を返すため、このエラーは実際には未定義の動作を引き起こす可能性があります。呼び出し規約とメモリ レイアウトによっては、スタック スマッシングmallocが発生する可能性があります。現代のコンパイラでは、この問題が見過ごされる可能性は低い。C99では暗黙的な宣言が認められていないため、コンパイラは戻り値を想定する場合でも診断メッセージを生成する必要があるからだ。さらに、ポインタの型が宣言時に変更された場合、が呼び出され、キャストが行われている行も更新する必要がある。intmalloc
C++では、どうしてもstd::malloc使用する必要がある場合は、static_cast生のキャストを使用するよりも、以下の方法の方が望ましいです。
int * p = static_cast < int *> ( malloc ( 10 * sizeof ( * ptr )));動的メモリ割り当ての不適切な使用は、しばしばバグの原因となります。これには、セキュリティ上のバグやプログラムのクラッシュなどが含まれ、その多くはセグメンテーション違反が原因です。
最も一般的なエラーは次のとおりです。[ 15 ]
free適切に行わないと、プログラムで使用されなくなった再利用不可能なメモリが蓄積されます。これはメモリリソースの浪費につながり、リソースが枯渇すると割り当てエラーを引き起こす可能性があります。malloc、 を使用した割り当て、データを格納するための使用、 を使用した解放という同じパターンに従う必要がありますfree。 の呼び出し後free(ダングリングポインタ) または の呼び出し前malloc(ワイルドポインタ) でのメモリ使用、freeの2回呼び出し (ダブルフリー) など、このパターンに従わない場合、通常はセグメンテーション違反が発生し、プログラムがクラッシュします。これらのエラーは一時的なもので、デバッグが困難な場合があります。たとえば、解放されたメモリは通常、OS によってすぐに回収されないため、ダングリングポインタがしばらくの間残って、正常に動作しているように見えることがあります。さらに、ANSI C 標準化に先行するインターフェースとして、mallocおよびそれに関連する関数には、実装が定義するように意図的に残された動作があります。その 1 つは、ゼロ長の割り当てです。これは、reallocサイズをゼロにすることがより一般的であるため、より大きな問題となります。[ 16 ] POSIXとSingle Unix Specification の両方で、0 サイズの割り当てを適切に処理するためにNULL、または安全に解放できる何かを返すことが求められていますが、[ 17 ]すべてのプラットフォームがこれらのルールに従う必要はありません。これにより発生した多くの二重解放エラーの中で、2019 年のWhatsApp RCE は特に目立っていました。[ 18 ]これらの関数をより安全にするためのラップ方法は、単にサイズ 0 の割り当てをチェックして、サイズ 1 の割り当てに変換することです。( を返すことにはNULL独自の問題があります。そうでなければ、メモリ不足エラーを示します。 の場合、realloc元のメモリが移動および解放されていないことを通知しますが、これもサイズ 0 の場合は当てはまらないため、二重解放につながります。) [ 19 ]
メモリ管理の実装は、オペレーティングシステムとアーキテクチャに大きく依存します。一部のオペレーティングシステムはmalloc用のアロケータを提供しますが、他のオペレーティングシステムはデータの特定領域を制御する関数を提供します。同じ動的メモリ割り当て器は、C++のmalloc演算子と両方を実装するためによく使用されます。[ 20 ]new
従来のメモリ割り当て器の実装は、一般的にヒープセグメントを使用して行われていました。メモリ割り当て器は通常、割り当て要求を満たすためにヒープを拡張および縮小していました。
ヒープ方式には、いくつかの本質的な欠点がある。
ダグ・リーは、汎用アロケータとしてパブリックドメインのdlmalloc(「ダグ・リーのMalloc」)を1987年から開発しました。GNU Cライブラリ(glibc)は、スレッド関連の改良を加えたdlmallocのフォークであるWolfram Glogerのptmalloc(「pthreads malloc」)から派生しています。[ 21 ] [ 22 ] [ 23 ] 2023年11月現在、dlmallocの最新バージョンは2012年8月のバージョン2.8.6です。2023年にCC0からMIT-0にライセンスが変更されました。[ 24 ]
dlmalloc は境界タグアロケータです。ヒープ上のメモリは「チャンク」として割り当てられます。チャンクは、ヘッダーと使用可能なメモリを含む8 バイト境界にアラインされたデータ構造です。割り当てられたメモリには、チャンクのサイズと使用フラグのための 8 バイトまたは 16 バイトのオーバーヘッドが含まれます (ドープ ベクトルに似ています)。割り当てられていないチャンクには、使用可能な領域内の他の空きチャンクへのポインタも格納されるため、最小チャンクサイズは 32 ビット システムでは 16 バイト、64 ビット システムでは 24/32 (アラインメントによって異なります) バイトになります。[ 22 ] [ 24 ] : 2.8.6、最小割り当てサイズ
未割り当てメモリは、類似サイズの「ビン」にグループ化され、チャンクの二重リンクリスト(チャンク内の未割り当て領域にポインタが格納される)を使用して実装されます。ビンはサイズによって次の3つのクラスに分類されます。[ 22 ] [ 24 ]:オーバーレイデータ構造
ゲーム開発者のエイドリアン・ストーンはdlmalloc、境界タグアロケータである は、仮想メモリは持っているがデマンドページングがないコンソールシステムには不向きだと主張している。これは、そのプール縮小および拡張コールバック(sysmalloc/ systrim)を使用して仮想メモリの個々のページを割り当ててコミットできないためである。デマンドページングがない場合、断片化がより大きな問題となる。[ 27 ]
FreeBSD 7.0 およびNetBSD 5.0以降、古いmalloc実装 ( Poul-Henning Kampphkmallocによる) はJason Evans によって書かれたjemallocに置き換えられました。その主な理由は、マルチスレッドに関してのスケーラビリティの欠如でした。ロック競合を回避するために、は各CPUに対して別々の「アリーナ」を使用します。マルチスレッドアプリケーションで 1 秒あたりの割り当て数を測定する実験では、これによりスレッド数に比例してスケーリングされることが示されましたが、phkmalloc と dlmalloc の両方では、パフォーマンスはスレッド数に反比例していました。[ 28 ]phkmallocjemalloc
OpenBSDのこの関数の実装では、 mmapmallocを使用します。1 ページより大きいサイズの要求の場合、割り当て全体がを使用して取得されます。より小さいサイズは、によって管理されるメモリ プールから割り当てられます。これらの「バケット ページ」もで割り当てられます。[ 29 ]を呼び出すと、メモリはを使用してプロセスのアドレス空間から解放され、アンマップされます。このシステムは、OpenBSD のシステム コールの一部として実装されているアドレス空間レイアウトのランダム化とギャップ ページ機能を利用してセキュリティを向上させ、解放後使用バグを検出するように設計されています。大きなメモリ割り当ては解放後に完全にアンマップされるため、それ以上使用するとセグメンテーション違反が発生し、プログラムが終了します。mmapmallocmmapfreemunmapmmap
GrapheneOSプロジェクトは当初、OpenBSDのメモリ割り当て機能をAndroidのBionic Cライブラリに移植することから始まった。[ 30 ]
Hoard は、スケーラブルなメモリ割り当てパフォーマンスを目標とするアロケータです。OpenBSD のアロケータと同様に、Hoard はmmapのみを使用しますが、メモリをスーパーブロックと呼ばれる 64 キロバイトのチャンクで管理します。Hoard のヒープは、論理的に単一のグローバル ヒープと複数のプロセッサごとのヒープに分割されます。さらに、限られた数のスーパーブロックを保持できるスレッド ローカル キャッシュがあります。ローカルのスレッドごとまたはプロセッサごとのヒープ上のスーパーブロックからのみ割り当て、ほとんど空のスーパーブロックをグローバル ヒープに移動して他のプロセッサが再利用できるようにすることで、Hoard は断片化を低く抑えながら、スレッド数に対してほぼ線形のスケーラビリティを実現します。[ 31 ]
Microsoft Researchが開発した、パフォーマンスに重点を置いたオープンソースのコンパクトな汎用メモリ割り当てライブラリです。[ 32 ]このライブラリは約 11,000行のコードで構成されています。
各スレッドには、小さな割り当て用のスレッドローカルストレージがあります。大きな割り当てには、mmap またはsbrkを使用できます。Googleが開発したmallocであるTCMalloc [ 33 ]は、デッド スレッドのローカルストレージ用のガベージ コレクションを備えています。TCMalloc は、マルチスレッド プログラムの場合、glibc の ptmalloc の 2 倍以上高速であると考えられています。[ 34 ] [ 35 ]
オペレーティングシステムのカーネルはmalloc、アプリケーション プログラムと同様にメモリを割り当てる必要があります。ただし、カーネル内での実装は、C ライブラリで使用される実装とは大きく異なることがよくあります。たとえば、メモリ バッファはDMAによって課される特別な制限に準拠する必要がある場合や、メモリ割り当て関数が割り込みコンテキストから呼び出される場合があります。[ 36 ]このため、オペレーティングシステム カーネルの仮想メモリmallocサブシステムと密接に統合された実装が必要になります。
mallocおよびそれに関連する関数はプログラムのパフォーマンスに大きな影響を与える可能性があるため 、アプリケーションの割り当てパターンに合わせて最適化されたカスタム実装によって、特定のアプリケーションの関数をオーバーライドすることは珍しくありません。C 標準ではこれを行う方法が規定されていませんが、オペレーティングシステムは動的リンクを利用してこれを行うさまざまな方法を見出しています。1 つの方法は、シンボルをオーバーライドするために別のライブラリを単純にリンクすることです。もう 1 つは、Unix System V.3で採用されている方法で、アプリケーションがカスタム関数にリセットできる関数ポインタmallocを作成することです。 [ 37 ]free
ソースコードでは、プリプロセッサマクロを使用することで、この関数を簡単に置き換えることができます。
#define malloc custom_malloc #define realloc custom_realloc #define free custom_free
これらは、メモリ割り当て関数を使用するすべてのファイルに含める必要があるヘッダーで定義されます。これらのマクロはstrdup、関数も置き換える必要があります。なぜなら、その結果はにも渡されるからですfree。ライブラリ関数内で割り当てられたデータ(FILEの構造体などstdio.h)は、ラッパーを使用せずに直接処理されます。
POSIXライクなシステムで最も一般的な方法は、環境変数LD_PRELOADにアロケータのパスを設定することです。これにより、動的リンカはlibcの実装ではなく、そのバージョンのmalloc/calloc/freeを使用します。
割り当て可能な最大メモリブロックサイズは、mallocホストシステム、特に物理メモリのサイズとオペレーティングシステムの実装によって異なります。
理論的には、最大の数値は型に格納できる最大値であるべきでsize_t、これは実装依存の符号なし整数で、メモリ領域のサイズを表します。C99 規格以降では、SIZE_MAX定数として利用できます。ISO Cでは保証されていませんが、通常は です。<stdint.h>2^(CHAR_BIT * sizeof(size_t)) - 1
mallocglibc システムでは、割り当て可能な最大のメモリ ブロックは、このサイズの半分、つまり です。[ 38 ]2^(CHAR_BIT * sizeof(ptrdiff_t) - 1) - 1
MS-DOSの一部のメモリ モデルのようにセグメンテーションを使用するシステムでは、可能な最大のメモリ ブロックは通常 1 セグメント以下であり、したがってアドレス空間全体よりもかなり小さく、より小さいタイプになる可能性があります。size_tptrdiff_t
様々なオペレーティングシステムやコンパイラに付属するCライブラリの実装には、標準mallocインターフェースの代替機能や拡張機能が含まれている場合があります。その中でも特に注目すべきものは以下の通りです。
allocaは、コールスタック上に要求されたバイト数を割り当てます。通常、呼び出し元の関数が戻るとすぐにメモリが解放されるため、対応する解放関数は存在しません。は、 32/Valloca (1978)の早い時期から Unix システムで存在していましたが、一部の (組み込み) コンテキストではその使用が問題になる場合があります。[ 39 ]多くのコンパイラでサポートされていますが、 ANSI-C標準の一部ではないため、常に移植可能とは限りません。また、軽微なパフォーマンスの問題を引き起こす可能性もあります。可変サイズのスタック フレームにつながるため、スタック ポインタとフレーム ポインタの両方を管理する必要があります (固定サイズのスタック フレームでは、これらの 1 つが冗長です)。[ 40 ]より大きな割り当ては、スタック オーバーフローによる未定義動作のリスクを高める可能性もあります。[ 41 ] C99 では、代替のスタック割り当てメカニズムとして可変長配列が提供されていましたが、この機能は後のC11標準ではオプションになりました。 posix_memalign、呼び出し元が指定したアライメントでメモリを割り当てる関数を定義しています。その割り当ては で解放されるためfree、[ 42 ]通常は実装が malloc ライブラリの一部である必要があります。callocおよびおよびのcfree実装について説明しています。allocfreemanのページmallocは275ページに記載されています。{{cite book}}: CS1 maint: 非推奨のアーカイブサービス (リンク)コード: [リンク削除済み]訂正: