C言語は、コードを実行する上でのメモリ管理が特に重要になってくる言語の1つです。
中でも、配列など数多くのデータを扱う可能性のある場面では、適切なメモリ管理が重要になってきます。
メモリに関する関数の中で、配列などのサイズを動的に決定したい場合に必要となるのが、malloc関数です。
今回は、C言語で malloc関数を使用する方法について解説していきたいと思います。
C言語のmallocとは?
mallocは、プログラムを実行する際の必要分に合わせて、メモリを動的に確保する関数です。
その特性上、固定サイズでない配列など、必要なメモリサイズが変動するオブジェクトの使用時に活用されています。
また、malloc関数によって確保されたメモリ領域は、関数内で使用する変数など、ブロックの終わりごとにメモリが解放されるオブジェクトとは異なり、free関数を実行するまでメモリが保持され続ける特徴を持ちます。
そのため、関数の戻り値にポインタを返す場合など、割り当てたメモリをスコープ外で使用する際にも活用することができます。
mallocの使い方
malloc関数を使用する際は、「stdlib.h」 ファイルをインクルードする必要があります。
以下のコードを忘れずに記述しておきましょう。
#include <stdlib.h>関数の基本構文は以下の通りです。
( データ型* )malloc( sizeof(データ型) );malloc関数は、割り当てられたメモリ領域を示す void型のポインターを戻り値として返します。
そのため、必要に応じて 「( データ型* )」 でキャストするようにしましょう。
関数の引数には、取得するメモリサイズを指定します。
sizeof演算子を使用することで、対象のデータ型のメモリサイズをバイト単位で取得することができます。
取得したサイズはメモリ1つ分となるため、2つ以上のメモリを使用する場合は、必要な分だけ乗算して取得しましょう。
#include <stdio.h>
#include <stdlib.h>
int main(void){
int *numlist;
numlist = (int*)malloc(sizeof(int) * 5);
if (numlist == NULL) {
printf("malloc error\n");
return -1;
} else {
int i;
for (i = 0; i < 5; i++) {
numlist[i] = i + 1;
printf("%d\n", numlist[i]);
}
}
free(numlist);
return 0;
}1
2
3
4
5上記の例では、「(sizeof(int) * 5)」 で int型の要素5つ分のメモリを確保しています。
malloc関数で確保されたメモリ領域は配列のように連続した場所にあるため、配列と同様に添字でアクセスすることが可能です。
また、malloc関数を使用する際には、2つ注意するべきポイントが存在します。
1点目は、メモリ領域が足りないなどの理由でメモリを確保できない場合があることです。
その場合、関数は NULLを戻り値として返します。
確保されていない領域にアクセスしようとしてしまうと当然エラーが発生してしまうため、実際に malloc関数を使用する時は必ず NULLチェックを行うようにしましょう。
2点目は、確保された領域がプログラムの終了時以外に自動で解放されることがないことです。
そのため、不要になった段階で free関数を使用し、手動で解放する必要があります。
メモリの解放を忘れると、確保した領域が残り続けて圧迫してしまう原因となるため、忘れず実行するようにしましょう。
mallocで確保したメモリを扱う様々な関数
確保したメモリ領域を初期化したり、別の領域に値をコピーしたりなど、合わせて使用すると便利な関数がいくつか存在するので、合わせて紹介したいと思います。
それぞれ、順に確認していきましょう。
memset関数
malloc関数で確保された領域は初期化が行われていないため、使用前に値を手動で入れておく必要があります。
メモリの確保後に即座にデータを格納する場合には特に必要ありませんが、事前に同一の値で初期化をしておきたいケースなどで使用すると便利なのが、memset関数です。
memset関数を使用する場合は、「string.h」 ファイルをインクルードする必要があります。
関数の基本構文は以下の通りです。
memset(メモリアドレス, 初期値, バイト数)第1引数には、初期化したい領域のアドレスを指定します。
malloc関数の戻り値は確保したメモリ領域の先頭アドレスとなっているため、そのまま戻り値を格納したポインタ変数を指定しましょう。
第2引数には初期値を、第3引数には初期化するメモリのバイト数を指定します。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
int *numlist;
numlist = (int *)malloc(sizeof(int) * 5);
if (numlist == NULL) {
printf("malloc error\n");
return -1;
} else {
memset(numlist, 0, sizeof(numlist));
int i;
for (i = 0; i < 5; i++) {
printf("%d\n", numlist[i]);
}
}
free(numlist);
return 0;
}0
0
0
0
0上記の例では、すべての要素を0で一旦初期化しています。
実行結果を見ると、同じ値で問題なく初期化されているのが分かりますね。
このように、memset関数を使って初期化することで、コードをより簡潔に記述することができます。
memcpy関数
確保した領域の内容を別の領域へコピーしたい際に使用すると便利なのがmemcpy関数です。
memcpy関数を使用する際は、memset関数と同様に「string.h」 ファイルをインクルードする必要があります。
基本構文は以下のとおりです。
memcpy(コピー先アドレス, コピー元アドレス, バイト数)第1引数にコピー先のメモリアドレス、第2引数にコピー元のメモリアドレスを指定します。
記述の際には順番を間違えないように注意しましょう。
第3引数には、コピーするメモリのバイト数を指定します。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
int *numlist1;
int *numlist2;
numlist1 = (int *)malloc(sizeof(int) * 2);
numlist2 = (int *)malloc(sizeof(int) * 2);
if (numlist1 == NULL || numlist2 == NULL) {
printf("malloc error\n");
return -1;
} else {
int i;
for (i = 0; i < 5; i++) {
numlist1[i] = i + 10;
numlist2[i] = i + 20;
}
printf("コピー前\n");
for (i = 0; i < 2; i++) {
printf("%d\n", numlist1[i]);
}
memcpy(numlist1, numlist2, sizeof(numlist2));
printf("コピー後\n");
for (i = 0; i < 2; i++) {
printf("%d\n", numlist1[i]);
}
}
free(numlist1);
free(numlist2);
return 0;
}コピー前
10
11
コピー後
20
21使用時の注意点として、コピー先のメモリ領域とコピー元のメモリ領域の一部が被っている場合に、memcpy関数では挙動が保証されていません。
領域が重なる可能性がある場合は、memmove関数を使用するようにしましょう。
また、上記の例のように通常の配列や文字列をコピーする際は問題ありませんが、ポインタメンバを持つ構造体の場合、格納されているアドレスのみがコピーされるため
注意が必要です。
このようなコピーはシャローコピー(浅いコピー) と呼ばれ、コピー先とコピー元で同じアドレス先を参照することになるため、片方の値を変更するともう片方の値も変更されてしまうデメリットが生じます。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
int age;
char *name;
} Person;
int main(void) {
Person *a, *b;
a = (Person *)malloc(sizeof(Person));
b = (Person *)malloc(sizeof(Person));
a->age = 15;
a->name = (char *)malloc(sizeof(char) * 20);
strcpy(a->name, "Taro Yamada");
memcpy(b, a, sizeof(Person));
printf("a.name:%s\n", a->name);
printf("b.name:%s\n", b->name);
strcpy(a->name, "Jiro Tanaka");
printf("a.name:%s\n", a->name);
printf("b.name:%s\n", b->name);
free(a);
free(b);
return 0;
}a.name:Taro Yamada
b.name:Taro Yamada
a.name:Jiro Tanaka
b.name:Jiro Tanakaこのようなケースの場合は、構造体ごとではなく個別のメンバごとにコピーするようにしましょう。
memcmp関数
メモリ領域ごとの内容を比較したい場合に使用可能なのが、memcmp関数です。
memcmp関数は、指定したメモリ同士の値の大小を比較し、その結果を以下の数値で返します。
- データが一致する場合 … 0 を返却
- 比較元のデータが大きい場合 … 正の値を返却
- 比較元のデータが小さい場合 … 負の値を返却
基本構文は以下の通りです。
memcmp(比較元のアドレス, 比較先のアドレス, バイト数);実際の動きを見てみましょう。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void) {
char *str1, *str2;
int result;
str1 = (char *)malloc(sizeof(char) * 5);
str2 = (char *)malloc(sizeof(char) * 5);
strcpy(str1, "Hello");
strcpy(str2, "Hello");
result = memcmp(str1, str2, sizeof(char) * 5);
printf("結果:%d\n", result);
free(str1);
free(str2);
return 0;
}比較結果:0
比較結果:-29上述の結果を見ると分かるように、同じ文字列の場合は0、違う場合は0以外の数値が返却されています。
文字列の比較を行いたい場合は、戻り値が0かどうかで条件分けを行うことができます。
数値などの場合は、戻り値が正の値か負の値かで、大小の比較を行うことも可能です。
まとめ
いかがでしたか?今回は、malloc関数の使い方について解説しました。
他の言語に比べても、メモリ周りを意識してコードを記述する必要性が高いC言語では、特に重要な関数の1つとなってきます。
関数の使い方をしっかりと把握し、実際の開発に役立てられるよう事前に練習しておくといいでしょう。
また、メモリアドレスやポインタ変数の使い方などに関する知識も C言語では重要となってくるため、こちらも合わせて確認しておくことをおすすめします。
C言語の勉強方法は?
書籍やインターネットで学習する方法があります。昨今では、YouTubeなどの動画サイトやエンジニアのコミュニティサイトなども充実していて多くの情報が手に入ります。
そして、より効率的に知識・スキルを習得するには、知識をつけながら実際に手を動かしてみるなど、インプットとアウトプットを繰り返していくことが重要です。特に独学の場合は、有識者に質問ができたりフィードバックをもらえるような環境があると、理解度が深まるでしょう。
ただ、C言語に限らず、ITスキルを身につける際、どうしても課題にぶつかってしまうことはありますよね。特に独学だと、わからない部分をプロに質問できる機会を確保しにくく、モチベーションが続きにくいという側面があります。独学でモチベーションを維持する自信がない人にはプログラミングスクールという手もあります。費用は掛かりますが、その分スキルを身につけやすいです。しっかりと知識・スキルを習得して実践に活かしたいという人はプログラミングスクールがおすすめです。
プログラミングスクールならテックマニアがおすすめ!
ITスキル需要の高まりとともにプログラミングスクールも増えました。しかし、どのスクールに通うべきか迷ってしまう人もいるでしょう。そんな方にはテックマニアをおすすめします!これまで多くのITエンジニアを育成・輩出してきたテックマニアでもプログラミングスクールを開講しています。
<テックマニアの特徴>
・たしかな育成実績と親身な教育 ~セカンドキャリアを全力支援~
・講師が現役エンジニア ~“本当”の開発ノウハウを直に学べる~
・専属講師が学習を徹底サポート ~「わからない」を徹底解決~
・実務ベースでスキルを習得 ~実践的な凝縮カリキュラム~
このような特徴を持つテックマニアはITエンジニアのスタートラインとして最適です。
話を聞きたい・詳しく知りたいという方はこちらからお気軽にお問い合わせください。