検索
特集

CとC++の動的メモリ管理(1)、malloc関数とnew演算子の違いを知る組み込み技術(1/2 ページ)

C言語とC++言語では、動的にメモリを確保したり解放したりする手法が異なり、それぞれ長所と短所がある。もちろん、安全性を最重要視する組み込み機器では、動的に確保したメモリを利用すべきでないという考え方には心から賛同する。

PC用表示
Share
Tweet
LINE
Hatena

 C言語とC++言語では、動的にメモリを確保したり解放したりする手法は異なり、それぞれ長所と短所がある。もちろん、安全性を最重要視する組み込み機器では、動的に確保したメモリを利用すべきでないという考え方には心から賛同する。リスクが利点を上回ってしまうからだ。しかし場合によっては、動的なメモリを適切に管理することで改善できることも多いのではないかとも考えている。

 C言語やC++言語に標準的に用意されているメモリ管理向け関数の動作が意図した通りでない場合は、メモリ管理関数を独自に開発するとよい。独自のメモリ管理関数の仕様と振る舞いは、できる限り標準関数と同じにすることが理想である。標準関数のメモリ管理方法と違ってしまう場合でも、関数の引数と返り値の数と型は、可能な限り標準関数にそろえるべきである。関数の仕様を同一にしておくことで、最初は標準のメモリ管理関数を使って開発し、必要に応じて独自のメモリ管理関数を使うように改変したり、場合によっては標準のメモリ管理関数を使うように戻したりすることが容易になる。

 動的なメモリ管理の方法を知っておくことは、C++開発者にとって大変価値がある。C++は、メモリリークの発生を減少させる手段を数多く提供している。その代表的なものが、コンストラクタとデストラクタを備えるクラスである。既存のソースコードに専用のメモリ管理機構を簡単に追加でき、メモリマップドI/Oを用いたデバイスレジスタを表現するオブジェクトを配置する際にさえ、専用のメモリ管理関数と入れ替えて使うことができる。

 本稿は、C言語とC++言語が標準的に備えているメモリ管理手法を比較する。C/C++のどちらを使うかにかかわらず、これらの違いを理解することは有用である。

C言語のメモリ管理

 標準Cでは、2つのメモリ確保関数(mallocとcalloc)と、1つのメモリ解放関数(free)、そして確保済みメモリ領域の大きさを変更する(メモリ確保と解放を同時に行う)realloc関数を提供している。これら4つの関数は、全て標準ヘッダファイル「stdlib.h」内でプロトタイプ宣言されている。

 メモリ確保関数(mallocやcalloc、realloc)で確保した領域は、その領域のポインタ(先頭アドレス)をメモリ解放関数(freeあるいはrealloc)に渡すまで、確保されたまま残る。未確保の領域を解放しようとした場合の動作は未定義である。

 malloc関数は、以下のように宣言されている。

void *malloc(size_t size);

 「malloc(s)」のように呼び出すと、malloc関数はsバイトの大きさのメモリをヒープ領域から確保しようとする。メモリの確保に成功すると、そのメモリ領域のポインタを返す。それ以外の場合は、ヌルポインタが返る。

 引数sizeの型はsize_tだ。size_tは、「stdlib.h」など幾つかのヘッダファイルの中で定義(typedef)されている型である。この型は、幾つかの符号なし整数型の別名となっている。主な例としてunsigned int型やunsigned long型が挙げられるが、unsigned long long型が用いられることもある。標準Cでは、size_tを、ターゲットとするプラットフォーム上で生じ得る最大のオブジェクト(変数)の大きさを表現できるよう十分なバイト数があり、しかも必要以上に大きすぎない符号なし整数型にするよう定めている。

 malloc関数の典型的な呼び出しでは、引数にsizeof式を与える(sizeof式はsize_t型の値を返す)。例えば変数pが、widget型の値へのポインタであるとき、次のように記述すると、widget型の値を格納するメモリ領域を動的に確保し、そのポインタをpに代入する。

p = malloc(sizeof(widget));

 また次のような文では、widget型の配列(要素数10)を格納するメモリ領域を動的に確保し、そのポインタをpに代入する。

p = malloc(10 * sizeof(widget));

 一方、calloc関数は、malloc関数の代わりに使える関数で、以下のように宣言されている。

void *calloc(size_t nmemb, size_t size);

 「calloc(n, s)」のように呼び出すと、要素数nの配列に必要なメモリをヒープ領域から確保する。各要素の大きさはsバイトである。malloc関数と同様、メモリの確保に成功すると、そのメモリ領域へのポインタを返す。それ以外の場合は、ヌルポインタを返す。

 従って、10個のwidgetからなる配列を確保するには、以下の2つの方法があることになる。

p = malloc(10 * sizeof(widget));
p = calloc(10, sizeof(widget));

 これら2つの関数の本質的な違いは、確保したメモリ領域に格納される値だ。malloc関数で確保した領域の値は不定である。一般に、malloc関数を呼び出す前にそのメモリ領域に格納されていた値がそのまま残る。それに対してcalloc関数では、確保したメモリ領域の全ビットがゼロにクリアされる。

 realloc関数は、前述の通り、malloc関数やcalloc関数で確保したメモリ領域の大きさを変更するときに使う。以下のように宣言されている。

void *realloc(void *ptr, site_t size);

 「realloc(p, s)」の結果は、pがヌルポインタの場合、「malloc(s)」と同じである。pがヌルポインタでない場合、新しいメモリ領域(大きさはs)を確保し、pが示すメモリ領域を解放した上で、新領域のポインタを返す。realloc関数を呼び出す前にpが示すメモリ領域に格納されていた内容は、新しいメモリ領域に引き継がれる。ただし引き継げるのは、新旧メモリ領域の、どちらか小さい方の領域の大きさに相当する内容だけである。新領域が旧領域よりも大きい場合、新しい領域の、古い領域を超えた部分の値は不定である。他のメモリ確保用関数と同様、新しいメモリ領域の確保に成功すると、そのメモリ領域へのポインタを返す。それ以外の場合は、古いメモリ領域を解放することなくヌルポインタを返す。

 size_tが常に符号なし(unsigned)型なので、malloc/calloc関数は、確保するメモリ領域の大きさを指定する引数を負の数ではないものとして解釈する。ただし、引数としてゼロを与えることはできる。

p = malloc(n * sizeof(widget));

 例えば、上記の文でnがゼロだった場合、malloc関数は要素数がゼロの配列を確保しようとする。返り値がヌルポインタであるか、ヌルでないポインタであるかは、処理系(コンパイラ)に依存する。どちらの場合も、返り値のポインタが示すメモリ領域に対して解放処理を実行しようとした場合、その結果は未定義である。

 free関数は、以下のように宣言されている。

void free(void *ptr);

 「free(p)」と呼び出したときのpがヌルポインタの場合、free関数は何もしない。それ以外の場合は、pが示すメモリ領域を解放する。

Copyright © ITmedia, Inc. All Rights Reserved.

       | 次のページへ
ページトップに戻る