みなさんmallocを使ってますか?
mallocは配列や構造体のメモリを動的に確保するために使用する関数です。
確保したメモリ領域を関数を使って操作したい
今回はそんな人たちに向けて、以下のような内容をまとめました。
- [基本]mallocとは
- [基本]mallocの使い方について
- [応用]memsetの使い方
- [応用]memcpyの使い方
- [応用]memcmpの使い方
mallocで確保したメモリはmemset、memcpy、memcmpなどの関数を使って初期化、コピーもしくはバイト単位での比較が可能です。汎用性の高い処理を書くために、必要な技術です。
ぜひmallocについて、詳しくなりましょう!
また、長い説明は要らない、使い方だけ知りたい!そんな方は以下のリンク先に、情報をひとまとめにしておきましたので、そちらをご覧ください。
mallocとは
mallocとは動的メモリを確保する関数です。
ヘッダーファイル「stdlib.h」で宣言されています。引数で指定するバイト数分のメモリが確保され、確保したメモリ領域へのポインタを返します。
確保したメモリはfree関数で必ず解放する必要があります。
mallocの使い方について
配列や構造体のように要素数やメンバによって確保するメモリの量が異なる場合について、mallocを使ってメモリを確保する方法について説明します。
malloc関数を使用するにはヘッダーファイル「stdlib.h」をインクルードする必要があります。mallocの引数にはsizeof関数を使って構造体の型や配列の要素数を指定し必要なバイト数を入力します。これを構造体の型のポインタや配列のポインタでキャストして使用します。
なお、確保したメモリはfree関数を使って解放するのを忘れないようにしましょう。
#include <stdio.h> #include <stdlib.h> // 構造体の宣言 typedef struct { int num; char *str; } strct; int main(void) { // ポインタ型の変数を生成 strct *entity; // 動的メモリの確保 entity = (strct*)malloc(sizeof(strct)); // メンバの初期化 entity->num = 0; entity->str = (char*)malloc(sizeof(char) * 32); // メモリに文字列を代入 sprintf(entity->str, "%s %s!", "Hello", "World"); printf("%s\n", entity->str); // メモリの解放 free(entity->str); free(entity); return 0; }
実行結果:
Hello World!
このサンプルコードではint型と文字列ポインタをメンバに持つstrct型の構造体を宣言しています。malloc関数を使ってstrct型のサイズのメモリを確保し、strct型ポインタにキャストしています。
また文字を入れるための「char型の領域」を32個分メモリ上へ確保し、文字列ポインタにキャストしています。sprintf関数を使って文字列ポインタに文字列を指定し、printf関数を使って文字列を表示しています。
最後にfree関数を使ってメンバの文字列ポインタと構造体の実体のメモリを解放しています。
その他の関数の使い方について
malloc関数を使って確保したメモリを扱う関数について説明します。メモリを扱う関数にはmemset、memcpy、memcmpなどの関数があります。
memsetの使い方について
memset関数は主にメモリを初期化する場合に使用します。
memset関数を使用するにはヘッダーファイル「string.h」をインクルードする必要があります。
memset関数の第1引数にはメモリを設定するオブジェクトのアドレスを、第2引数にはメモリにセットする値を、第3引数にはメモリにセットする値の文字数を入力します。
#include <stdio.h> #include <stdlib.h> #include <string.h> // 構造体の宣言 typedef struct { int num; char *str; } strct; int main(void) { // ポインタ型の変数を生成 strct *entity; // 動的メモリの確保。 entity = (strct*)malloc(sizeof(strct)); // メンバの初期化 entity->num = 0; entity->str = (char*)malloc(sizeof(char) * 32); // メモリに文字列を代入 sprintf(entity->str, "%s %s!", "Hello", "World"); printf("%s\n", entity->str); char arr_str[] = "Hello USA!"; // メモリの解放(変更する前に必ず解放しましょう) free(entity->str); // メモリサイズの変更 entity->str = (char*)malloc(sizeof(arr_str)); if(entity->str == NULL) { printf("memory error"); return -1; } // アドレスの先頭からarr_strのバイト数分だけNULL文字で書き換え memset(entity->str, '\0', sizeof(arr_str)); printf("%s\n", entity->str); // メモリの解放 free(entity->str); free(entity); printf("processing completion\n"); return 0; }
実行結果:
Hello World! processing completion
このサンプルコードではmalloc関数を使ってメモリのサイズの変更を行い、その後memset関数を使って文字列のバイト数分だけNULL文字(\0)でメモリの値を書き換えています。
NULL文字で書き換えた文字列ポインタを出力表示していますので、空行が1列出力されています。
memcpyの使い方について
memcpy関数を使って構造体の実体をコピーする方法を説明します。
memcpy関数は第1引数にコピー先のアドレス、第2引数にコピー元のアドレス、第3引数にはコピーするバイト数を入力します。オブジェクト全体をコピーする場合、第3引数はコピー元のオブジェクト全体のバイト数となります。
memcpy関数でコピー元が構造体の場合にメンバにポインタを持っていると注意する必要があります。memcpy関数を使って構造体全体をコピーするとメンバのポインタは浅いコピーとなり、コピー元の値を参照します。
したがってコピー元のポインタ先の値が変われば、コピー先のポインタの値も一緒に変わります。深いコピーをするためにはメンバのポインタは個別にコピーをする必要があります。
サンプルコードで確認しましょう。
#include <stdio.h> #include <stdlib.h> #include <string.h> // 構造体の宣言 typedef struct { int num; char *str; } strct; int main(void) { // ポインタ型の変数を生成 strct *entity; // 動的メモリの確保 entity = (strct*)malloc(sizeof(strct)); // メンバの初期化 entity->num = 0; entity->str = (char*)malloc(sizeof(char) * 32); // メモリに文字列を代入 sprintf(entity->str, "%s %s!", "Hello", "World"); printf("%s\n", entity->str); //構造体の実体のコピー strct *copy_entity; copy_entity = (strct*)malloc(sizeof(strct)); memcpy(copy_entity, entity, sizeof(strct)); // メンバのポインタは浅いコピー // 浅いコピーのため、コピー元の値が変わるとコピー先の値も変わる strcpy(entity->str, "Hello Japan!"); printf("%s, %s\n", entity->str, copy_entity->str); // 深いコピーにするためには、メンバ個別でコピーが必要 copy_entity->str = (char*)malloc(sizeof(char) * 32); strcpy(copy_entity->str, entity->str); // 深いコピーのため、コピー元の値が変わってもコピー先の値は変わらない strcpy(entity->str, "Hello USA!"); printf("%s, %s\n", entity->str, copy_entity->str); // メモリの解放 free(copy_entity->str); free(copy_entity); free(entity->str); free(entity); return 0; }
実行結果:
Hello World! Hello Japan!, Hello Japan! Hello USA!, Hello Japan!
このサンプルコードではmemcpy関数を使って、構造体の実体「entity」を「copy_entity」にコピーしています。
このままではcopy_entityのメンバ「str」の値はentityのメンバ「str」の値を参照します。entityのメンバ「str」の値を変更すると、copy_entityのメンバ「str」の値も一緒に変わっています。
そこで値を参照せず深いコピーにするために、メンバの「str」はstrcpy関数を使って個別にコピーをしています。個別のコピー後にコピー元のentityのメンバ「str」の値を変更しても、copy_entityのメンバ「str」の値は変更されず、深いコピーができています。
memcmpの使い方について
memcmp関数を使うと指定バイト数のメモリ領域を比較することができます。
memcmp関数の第1引数と第2引数には比較するオブジェクトのポインタを、第3引数には比較するバイトサイズを指定します。memcmp関数はint型の値を返し、戻り値が0(ゼロ)の場合は比較の結果オブジェクトは一致と判定され、それ以外の値の場合はオブジェクトは不一致と判定されます。
今回はシンプルなサンプルで見ていきましょう。
#include #include #include int main(void) { // 変数の用意 char *str1; char *str2; // 動的メモリの確保 str1 = (char*)malloc(sizeof(char) * 32); str2 = (char*)malloc(sizeof(char) * 32); // メモリの初期化 memset(str1, '\0', sizeof(char) * 32); memset(str2, '\0', sizeof(char) * 32); // 文字列を入れる strcpy(str1, "ABCDE!"); strcpy(str2, "ABCDE!"); // 比較する if( memcmp( str1, str2, (sizeof(char) * 32) ) == 0 ){ printf("同一の内容です!"); }else{ printf("同一の内容ではありません!"); } // 解放処理 free(str1); free(str2); return 0; }
実行結果:
同一の内容です!
このサンプルは非常にシンプルです。
str1・str2という二つのポインタ変数を用意し、それを比較しているだけですね!
[補足]memcmpによる構造体の比較
memcmp関数は構造体ではなく、シンプルサンプルで使い方をみてきました。
実はmemcmp構造体の実体を比較することは推奨されていません。パディングにより比較結果に不具合が発生する可能性があるからです。
パンディングとは奇数個の要素をもつchar型配列をメンバとしてもつ場合に、処理環境によっては1バイト空領域を追加して偶数個に揃える処理のことです。
これによりあるメンバのアドレス先からズレが生じることもありますので、memcmp関数を使って評価するのは避けましょう。比較を行いたい場合は、自作の関数を用意し、各メンバ変数の比較を行うのがメジャーな手法です。
その際は、以下の記事の「比較演算する方法」項目を参考にしてみてください。
各関数の使い方をひとまとめ
最後に復習として、各関数の使い方ををシンプルに一通りみてみましょう。
動的に確保する「malloc関数」
メモリを確保するなら、malloc関数です。使い方は、引数に確保したいバイト数を指定するだけでしたね!
// 動的メモリの確保 strct* entity = (strct*)malloc(sizeof(strct)); // 動的メモリの解放 free(entity);
また使い終わったら必ずfree関数で解放を行いましょう。動的にメモリを確保したら、ちゃんと解放する。
確保と解放はワンセットです。
データを埋めるなら「memset関数」
memset関数は、指定した領域を、指定した値で全て上書きしてくれる関数です。
主に初期化に使われる関数です。
// メモリを確保 char* str = (char*)malloc(sizeof(char) * 32); // \0で、メモリの指定領域を上書きしたい場合 memset(str, '\0', sizeof(char) * 32);
引数:
第一引数・・・対象のメモリのポインタ
第二引数・・・上書きする値を指定
第三引数・・・上書きするバイト数
コピーするなら「memcpy関数」
データのコピーならばmemcpy関数を使用しましょう。
// char型32個分の領域をコピーしたい場合 memcpy(str1, str2, sizeof(char) * 32);
引数:
第一引数・・・コピー先のポインタ
第二引数・・・コピー元のポインタ
第三引数・・・コピーするバイト数
比較するなら「memcmp関数」
二つの指定した領域が同じかどうかはmemcmp関数で比較できます。同じ場合は、0が戻り値として取得でるので、それをif分で判定しましょう。
また本編で説明があったように、構造体の比較は正確に行えないので注意しましょう。
if( memcmp(str1, str2, sizeof(char) * 32) == 0) { printf("同じです"); } else { printf("違います"); }
引数:
第一引数・・・比較するポインタ1
第二引数・・・比較するポインタ2
第三引数・・・比較するバイト数
まとめ
ここでは、mallocの使い方について説明しました。
またmemset、memcpy、memcmpといった関数を使ってメモリを操作する方法について説明しました。ポインタをメンバに持つ構造体についてmemcpy関数を使ってコピー操作する場合は実体全体のコピーでは浅いコピーとなるので注意しましょう。
使いこなすことができるように、この記事を何度も参考にして下さいね!