Index

HOME > プログラムTOP > C++



C++の壺・メモリ篇

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

sasaraan programming

Exposition

●バッファ操作

 外部変数と静的変数(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とfree

 メモリの動的確保には、高レベルメモリ管理関数の 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;
} 

●newとdelete

 C++ では、メモリを動的に確保する方法として new 演算子が用意されています。ただし、定数 (const) や関数に使うことはできません(関数へのポインタは可)。また、new 演算子で作成したオブジェクトは、自動的に破棄されません。使い終わったら、必ず delete 演算子を使って、明示的にメモリを解放する必要があります。

・オブジェクトの作成 : new データ型名;
・オブジェクトの破棄 : delete ポインタ;(アドレス)

 new 演算子は、オブジェクトへのポインタ (配列の場合は最初の要素へのポインタ) を返します。この時、オブジェクトの作成に失敗した際は NULL (0) を返してきます。new 演算子は、通常、ポインタ変数を用意(下表参照)してから使用し、これらの戻り値を受け取れるようにします。
 一方、delete 演算子は、オブジェクトへのポインタに対して使用します。ただし、delete 演算子は new 演算子で作成したオブジェクトに対してのみ正常に動作します。
記述例単独のオブジェクト一次元配列多次元配列
・ newint *p;
p = new int;
int *p;
p = new int[30];
int (*p)[30];
p = new int[10][30];
・ deletedelete 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;
} 

www.sasaraan.net

(c) morijoh