|
C++の壺・メモリ篇
C++の基本テクニックのTipsです。メモリ篇では、メモリ領域(バッファ)の操作やメモリの動的確保の方法を扱います。これらは、より効率的なメモリの使用を実現します。
| [INDEX]
| ・バッファ操作 | ... メモリの初期化とコピー | (memset / memcpy) |
| ・mallocとfree | ... 高レベルのメモリ管理 | (malloc / calloc / realloc / free) |
| ・newとdelete | ... メモリを管理する演算子 | (new / delete) |

外部変数と静的変数(static)は、宣言と同時に 0 で初期化されます。一方、自動変数は初期化が行われません。変数がいずれの種類でも、 memset 関数を利用して、簡単に初期化を行うことができます。memset 関数は、対象バッファの先頭から、指定した文字数分だけ、指定した文字で初期化します。また、対象が構造体やクラスでも使用が可能です。
| memset function |
| ・書式 | void *memset( void *dest, int c, size_t n ); |
| ・機能 | メモリ領域の初期化 |
| ・引数 | dest...メモリ領域, c...設定値, n...文字数 |
| ・戻り値 | メモリ領域へのポインタ |
| ・要件 | memory.h または string.h のインクルード |
上記中、size_t は、sizeof 演算子の結果を表すための型名です。通常は unsigned int 型として定義されています。引数 n は、この sizeof 演算子を使用すると、文字数(バイト数)を確実に算出できます。
// メモリの初期化
#include <stdio.h> // printf
#include <conio.h> // getch
#include <string.h> // memset
// main 関数
void main()
{
char s[4];
int i;
// 初期化をしていない文字列
// → 出力(例):-52,-52,-52,-52
for (i=0; i<4; i++) printf("%d, ", s[i]);
// '\0'(ヌル文字)で初期化
memset(s, '\0', sizeof(s));
printf("\n");
// 初期化後の文字列
// → 出力 : 0, 0, 0, 0
for (i=0; i<4; i++) printf("%d, ", s[i]);
if (getch()) return;
} |
// メモリのコピー
#include <stdio.h> // printf
#include <conio.h> // getch
#include <string.h> // memcpy
// main 関数
void main()
{
// コピー元
int a[4] = {33, 44, 55, 66};
// コピー先
int n[4];
// コピー
memcpy(n, a, sizeof(a));
// 出力 → 33, 44, 55, 66
int i;
for (i=0; i<4; i++) {
printf("%d, ", n[i]);
}
if (getch()) return;
} |
また、バッファ間のコピーには memcpy 関数を利用することができます。(*注 ただし、コピー先とコピー元のメモリ領域が重なっている時は、正確にコピーできません。その恐れがあるときは、memmove 関数を使用する必要があります。)
| memcpy function |
| ・書式 | void *memcpy(void *dest, const void *src, size_t n); |
| ・機能 | メモリ領域間のコピー |
| ・引数 | dest...コピー先の領域, src...コピー元の領域, n...文字数 |
| ・戻り値 | コピー先メモリ領域へのポインタ |
| ・要件 | memory.h または string.h のインクルード |
バッファ操作には、これらの他にも、 memchr(検索)、 memcmp(比較)、 memmove(上書きコピー) などの関数があります。
メモリの動的確保には、高レベルメモリ管理関数の malloc または calloc を利用します。これらの関数は、C と C++ で共通に使用できますが、C++ の場合は、new 演算子と delete 演算子を使用した方が、より容易にメモリを管理することができます(次節参照)。
| malloc / calloc function |
| ・書式 | void *malloc(size_t size);
| void *calloc(size_t n, size_t size); |
| ・機能 | メモリの割り当て | メモリを割り当てて 0 で初期化 |
| ・引数 | size...バイト数 | n...要素数, size...一要素のバイト数 |
| ・戻り値 | 割り当てたメモリ領域へのポインタ | 割り当てたメモリ領域へのポインタ |
| ・要件 | stdlib.h か malloc.h のインクルード | stdlib.h か malloc.h のインクルード |
これらの関数で割り当てたメモリ領域は、 realloc 関数でサイズを変更することができます。この戻り値は、再割り当ての際、別の場所に移動することがあり、もとの領域と異なる場合があります。この時、古いデータは新しい領域にコピーされます。 なお、これらの関数が失敗した時は、NULL を返します。
| realloc / free function |
| ・書式 | void *realloc(void *p, size_t size);
| void free(void *p); |
| ・機能 | メモリの再割り当て | メモリ領域の解放 |
| ・引数 | p...メモリ領域, size...新しいバイト数 | p...メモリ領域 |
| ・戻り値 | 再割り当て後の領域へのポインタ | なし |
| ・要件 | stdlib.h か malloc.h のインクルード | stdlib.h か malloc.h のインクルード |
メモリの解放は free 関数を使用します。malloc, calloc, realloc の各関数で割り当てた領域は、使用後、free 関数で解放する必要があります。また、realloc 関数で p が有効の時で size に 0 を指定しても同様にメモリは解放されます。この時は NULL を返します。
// メモリの動的確保 : malloc と free
#include <stdio.h> // printf
#include <stdlib.h> // malloc, free
#include <conio.h> // getch
// main 関数
void main()
{
int n = 260; // 文字数
char *s;
// メモリの確保
// n*sizeof(char) でバイト数を算出
s = (char*)malloc(n * sizeof(char));
if (s == NULL) {
printf("メモリの割り当て失敗\n");
} else {
printf("メモリの割り当て成功\n");
// メモリの解放
free(s);
}
if (getch()) return;
} |
// メモリの動的確保 : calloc と realloc
#include <stdio.h> // printf
#include <stdlib.h> // calloc,realloc,free
#include <conio.h> // getch
// main 関数
void main()
{
int n = 100;
int *p;
if (!(p = (int*)calloc(n, sizeof(int)))) {
printf("メモリ割り当て失敗\n");
} else {
printf("メモリ割り当て成功\n");
int size = n * 2 * sizeof(int);
if (!(p = (int*)realloc(p, size))) {
printf("再割り当て失敗\n");
} else {
printf("再割り当て成功\n");
}
free(p);
}
if (getch()) return;
} |
C++ では、メモリを動的に確保する方法として new 演算子が用意されています。ただし、定数 (const) や関数に使うことはできません(関数へのポインタは可)。また、new 演算子で作成したオブジェクトは、自動的に破棄されません。使い終わったら、必ず delete 演算子を使って、明示的にメモリを解放する必要があります。
・オブジェクトの作成 : new データ型名;
・オブジェクトの破棄 : delete ポインタ;(アドレス)
new 演算子は、オブジェクトへのポインタ (配列の場合は最初の要素へのポインタ) を返します。この時、オブジェクトの作成に失敗した際は NULL (0) を返してきます。new 演算子は、通常、ポインタ変数を用意(下表参照)してから使用し、これらの戻り値を受け取れるようにします。 一方、delete 演算子は、オブジェクトへのポインタに対して使用します。ただし、delete 演算子は new 演算子で作成したオブジェクトに対してのみ正常に動作します。
| 記述例 | 単独のオブジェクト | 一次元配列 | 多次元配列 |
| ・ new | int *p; p = new int;
| int *p; p = new int[30];
| int (*p)[30]; p = new int[10][30]; |
| ・ delete | delete p; | delete [] p; | delete [] p; |
上記中、オブジェクトが配列の時の delete には、[]の記述が必要です。 また、多次元配列のポインタ宣言の際、ポインタ演算子 * は、配列の演算子 [] よりも優先度が低いので () で囲む必要があります。この時、最も左側以外の次元は、通常通り、定数または定数式でなければなりません。
// new/deleteの基本的な用法
#include <stdio.h> // printf
#include <conio.h> // getch
// main 関数
void main()
{
// 単独のオブジェクト
int *p;
p = new int;
*p = 88;
printf("%d\n", *p);
delete p;
// 一次元配列
int *q;
q = new int[5];
int i;
for (i=0; i<5; i++)
*(q+i) = i; // 代入
for (i=0; i<5; i++)
printf("%d, ", *(q+i)); // 出力
delete [] q;
if (getch()) return;
}
|
// 二次元配列のnew/delete
#include <stdio.h> // printf
#include <conio.h> // getch
#include <string.h> // strcpy
// main 関数
void main()
{
// オブジェクトの生成
char (*s)[9];
s = new char[3][9];
// 配列の要素に代入
strcpy(s[0], "apple");
strcpy(s[1], "grape");
strcpy(s[2], "melon");
int i;
for (i=0; i<3; i++) {
printf("%s\n", s[i]); //出力
}
// オブジェクトの破棄
delete [] s;
if (getch()) return;
} |
|