🖥️

【C言語超入門】 第22回 メモリ操作

2024/12/25に公開

https://youtu.be/sWtCdhvSDJA

四国めたん
\textcolor{pink}{四国めたん: }教師役ですわ

ずんだもん
\textcolor{lime}{ずんだもん: }生徒役なのだ

\footnotesize \textcolor{pink}{四国めたん:} 皆さん、こんにちは。四国めたんです

\footnotesize \textcolor{lime}{ずんだもん:} ずんだもんなのだ。こんにちはなのだ

\footnotesize \textcolor{pink}{四国めたん:} 今回もC言語のお勉強をしていきましょう

\footnotesize \textcolor{lime}{ずんだもん:} レッツゴーなのだ

\footnotesize \textcolor{pink}{四国めたん:} 前回は 大きなサイズの配列動的メモリ配置 についてお話ししましたわ

\footnotesize \textcolor{lime}{ずんだもん:} おぼえているのだ

\footnotesize \textcolor{pink}{四国めたん:} 今回はそれらの大きなサイズのメモリ領域に対する操作を簡単にする関数を見て行きますわ

\footnotesize \textcolor{lime}{ずんだもん:} 簡単になるのは良いことなのだ

\footnotesize \textcolor{pink}{四国めたん:} なお基本的なメモリ操作の関数は"string.h"で宣言されているので最初にインクルードする必要がありますわ

\footnotesize \textcolor{lime}{ずんだもん:} わかったのだ

memset

\footnotesize \textcolor{pink}{四国めたん:} まず最初はmemsetですわ

\footnotesize \textcolor{lime}{ずんだもん:} memset

\footnotesize \textcolor{pink}{四国めたん:} はい、指定したメモリ領域の各バイトを指定の値にセットする関数ですわ

\footnotesize \textcolor{lime}{ずんだもん:} お~、forループでセットする必要がなくなるのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、形式としてはvoid* memset(void* ptr, int value, size_t num)となりますわ

\footnotesize \textcolor{lime}{ずんだもん:} 詳しく説明してほしいのだ

\footnotesize \textcolor{pink}{四国めたん:} わかりましたわ

\footnotesize \textcolor{pink}{四国めたん:} まず、メモリ領域の先頭のポインタをptrにセットしますわ

\footnotesize \textcolor{lime}{ずんだもん:} mallocで取得したメモリ領域をセットするのか?

\footnotesize \textcolor{pink}{四国めたん:} それ以外にも配列などもセット可能ですわね

\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ

\footnotesize \textcolor{pink}{四国めたん:} 次に、値をセットする領域のサイズをバイト単位でnumにセットしますわ

\footnotesize \textcolor{lime}{ずんだもん:} バイト単位なのか

\footnotesize \textcolor{pink}{四国めたん:} はい、mallocと同様ですわね

\footnotesize \textcolor{lime}{ずんだもん:} ところでnumがメモリ領域のサイズより大きい場合はどうなるのだ?

\footnotesize \textcolor{pink}{四国めたん:} 指定通りにデータをセットしますわ

\footnotesize \textcolor{lime}{ずんだもん:} それって大丈夫なのか?

\footnotesize \textcolor{pink}{四国めたん:} いいえ、他の重要なデータを破壊する可能性があるので、大丈夫ではありませんわ

\footnotesize \textcolor{pink}{四国めたん:} ですので、numはメモリ領域のサイズを超えないようにして下さいね

\footnotesize \textcolor{lime}{ずんだもん:} りょうかいなのだ

\footnotesize \textcolor{pink}{四国めたん:} 最後に、セットする値はvalueで指定しますわ

\footnotesize \textcolor{lime}{ずんだもん:} int型 ということは、4バイトのデータを指定できるのか?

\footnotesize \textcolor{pink}{四国めたん:} いいえ、memsetは各バイトに値をセットする関係上、0~255(0x00~0xFF)の指定のみ有効ですわ

\footnotesize \textcolor{lime}{ずんだもん:} 255よりも大きな値をセットした場合はどうなるのだ?

\footnotesize \textcolor{pink}{四国めたん:} 関数内部でunsigned charにキャストされてからセットされますわ

\footnotesize \textcolor{lime}{ずんだもん:} unsigned char

\footnotesize \textcolor{pink}{四国めたん:} charが-128~127の整数を表すことができる型だというのは覚えていますわね

\footnotesize \textcolor{lime}{ずんだもん:} とうぜんなのだ

\footnotesize \textcolor{pink}{四国めたん:} それに対して、unsigned charは、0~255の正の整数を表すことができる型ですわ

\footnotesize \textcolor{lime}{ずんだもん:} unsigned符号なし という意味なので、正の整数なのか...

\footnotesize \textcolor{pink}{四国めたん:} その通りですわ

\footnotesize \textcolor{pink}{四国めたん:} ただ、意図しない値がセットされることになるので、0~255以外の値はセットしないことをお勧めしますわ

\footnotesize \textcolor{lime}{ずんだもん:} りょうかいなのだ

\footnotesize \textcolor{pink}{四国めたん:} ちなみに戻り値に関してはptrにセットした値がそのまま返りますわ

\footnotesize \textcolor{pink}{四国めたん:} では、サンプルコードを見ていきましょう

#include <stdio.h>
#include <string.h>

void main() {
  int a[] = {1, 2, 3};
  int size = sizeof(a);
  memset(a, 0, size);
  printf("aのサイズは%dバイトです。\n", size);
  printf("a[0]の値は%dです。\n", a[0]);
  printf("a[1]の値は%dです。\n", a[1]);
  printf("a[2]の値は%dです。\n", a[2]);
}

memsetの結果

\footnotesize \textcolor{lime}{ずんだもん:} 「aのサイズは12バイトです。」、「a[0]の値は0です。」、「a[1]の値は0です。」、「a[2]の値は0です。」と表示されたのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、今回は配列の要素を0クリアしていますわね

\footnotesize \textcolor{lime}{ずんだもん:} 結果として全ての要素は0と表示されたのだ

\footnotesize \textcolor{lime}{ずんだもん:} とりあえずプログラムの説明をおねがいするのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、まず#include <string.h>で"string.h"をインクルードしていますわ

\footnotesize \textcolor{lime}{ずんだもん:} メモリ操作の関数は"string.h"ファイルで宣言されているのだ

\footnotesize \textcolor{pink}{四国めたん:} そして配列aに初期値を設定していますわ

\footnotesize \textcolor{lime}{ずんだもん:} 各要素に0以外の値をセットしているのだ

\footnotesize \textcolor{pink}{四国めたん:} 次に配列のサイズをsizeof演算子で、バイト単位で取得しておきますわ

\footnotesize \textcolor{lime}{ずんだもん:} たしか、配列に対してはsizeof演算子でサイズをバイト単位で取得できたのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、でも 動的メモリ配置 で取得したメモリ領域についてはsizeof演算子でのサイズ取得はできませんわ

\footnotesize \textcolor{lime}{ずんだもん:} おぼえているのだ

\footnotesize \textcolor{pink}{四国めたん:} 次にmemsetで配列aの全体を0でクリアしていますわ

\footnotesize \textcolor{lime}{ずんだもん:} 配列名だけを指定すれば、配列へのポインタと同じになるのだ

\footnotesize \textcolor{pink}{四国めたん:} その通りですわ

\footnotesize \textcolor{pink}{四国めたん:} なおmemsetの"value"引数には、0や0xFFや0xEFなどの16進数、 a などのASCIIコードを指定することが多いですわ

\footnotesize \textcolor{lime}{ずんだもん:} そうなのか...

\footnotesize \textcolor{pink}{四国めたん:} また、memsetの"ptr"には、配列以外にもmallocで取得したメモリ領域や構造体等へのポインタを指定できますわね

\footnotesize \textcolor{lime}{ずんだもん:} いろいろと便利に使えそうなのだ

memcpy

\footnotesize \textcolor{pink}{四国めたん:} 次はmemcpyですわ

\footnotesize \textcolor{lime}{ずんだもん:} memcpy

\footnotesize \textcolor{pink}{四国めたん:} はい、memcpyは指定したメモリ領域間で、データをコピーしますわ

\footnotesize \textcolor{lime}{ずんだもん:} データのコピーは便利なのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、形式としてはvoid* memcpy(void* dst, const void* src, size_t num)となりますわ

\footnotesize \textcolor{lime}{ずんだもん:} 詳しく説明してほしいのだ

\footnotesize \textcolor{pink}{四国めたん:} わかりましたわ

\footnotesize \textcolor{pink}{四国めたん:} まず、コピー元のポインタをsrcに、コピー先のポインタをdstにセットしますわ

\footnotesize \textcolor{lime}{ずんだもん:} コピーするデータのサイズをバイト単位でnumにセットするのか?

\footnotesize \textcolor{pink}{四国めたん:} その通りですわ

\footnotesize \textcolor{pink}{四国めたん:} ちなみに、データの コピー ですので、元のデータが残ることが前提ですわ

\footnotesize \textcolor{pink}{四国めたん:} ですのでコピー元とコピー先のメモリ領域が重なっていてはいけませんわね

\footnotesize \textcolor{lime}{ずんだもん:} 領域が重なるとどうなるのだ?

\footnotesize \textcolor{pink}{四国めたん:} Visual Studioですと問題なくデータのコピーを行えますが、一般的には正常にコピーされない場合がありますわ

\footnotesize \textcolor{lime}{ずんだもん:} ところでnumにメモリ領域のサイズより大きな値をセットした場合はどうなるのだ?

\footnotesize \textcolor{pink}{四国めたん:} 指定通り、データをコピーしますわ

\footnotesize \textcolor{pink}{四国めたん:} 当然、他の重要なデータを破壊することになる可能性がありますわね

\footnotesize \textcolor{lime}{ずんだもん:} たいへんなのだ

\footnotesize \textcolor{pink}{四国めたん:} なお戻り値に関してはdstにセットした値がそのまま返りますわ

\footnotesize \textcolor{lime}{ずんだもん:} りょうかいなのだ

\footnotesize \textcolor{pink}{四国めたん:} では、サンプルコードを見ていきましょう

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SIZE (3)

void main() {
  int size_byte = sizeof(int) * SIZE;
  int a[SIZE] = {1, 2, 3};
  int* p = (int*)malloc(size_byte);
  if (p != NULL) {
    memset(p, 0, size_byte);
    memcpy(p, a, size_byte);
    printf("*pの値は%dです。\n", *p);
    printf("*(p+1)の値は%dです。\n", *(p + 1));
    printf("*(p+2)の値は%dです。\n", *(p + 2));
  }
  free(p);
  p = NULL;
}

memcpyの結果

\footnotesize \textcolor{lime}{ずんだもん:} mallocで確保したメモリ領域に、配列のデータをmemcpyでコピーしているのだ

\footnotesize \textcolor{pink}{四国めたん:} とりあえずプログラムの説明をしますわね

\footnotesize \textcolor{lime}{ずんだもん:} おねがいするのだ

\footnotesize \textcolor{pink}{四国めたん:} まず、mallocで配列aと同じサイズでメモリ領域を確保していますわ

\footnotesize \textcolor{lime}{ずんだもん:} メモリ領域のサイズはbyte_sizeとして int型 のサイズと配列の要素数から計算しているのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、sizeof(a)としてもOKですわね

\footnotesize \textcolor{pink}{四国めたん:} そして、memsetで確保したメモリ領域を0クリアした後、memcpyで配列aの全データをコピーしていますわ

\footnotesize \textcolor{lime}{ずんだもん:} memsetでの0クリアは必要なのか?

\footnotesize \textcolor{pink}{四国めたん:} はっきり言って必要ないのですが、0クリアによる初期化の例として入れてみましたわ

\footnotesize \textcolor{lime}{ずんだもん:} 大人の事情なのだ

\footnotesize \textcolor{pink}{四国めたん:} 最後にfreeでメモリ領域の解放をしていますわ

\footnotesize \textcolor{lime}{ずんだもん:} 忘れたら大変なのだ

memmove

\footnotesize \textcolor{pink}{四国めたん:} 次はmemmoveですわ

\footnotesize \textcolor{lime}{ずんだもん:} memmove

\footnotesize \textcolor{pink}{四国めたん:} はい、指定したメモリ領域間で、データをコピーする関数ですわ

\footnotesize \textcolor{lime}{ずんだもん:} memcpyとはどう違うのだ?

\footnotesize \textcolor{pink}{四国めたん:} memmovememcpyと異なり移動元と移動先のメモリ領域が重なっていても問題なくコピーが可能ということですわ

\footnotesize \textcolor{lime}{ずんだもん:} お~、それは便利なのだ

\footnotesize \textcolor{pink}{四国めたん:} ただ、重なっているメモリ領域については、コピーされたデータで上書きされますわ

\footnotesize \textcolor{lime}{ずんだもん:} なるほどなのだ

\footnotesize \textcolor{pink}{四国めたん:} 形式としてはvoid* memmove(void* dst, const void* src, size_t num)となりますわ

\footnotesize \textcolor{lime}{ずんだもん:} 形式はmemcpyと同じなのだ

\footnotesize \textcolor{pink}{四国めたん:} 注意点も同じで、移動するデータのサイズはメモリ領域より大きい値をセットしてはいけませんわ

\footnotesize \textcolor{lime}{ずんだもん:} 注意するのだ

\footnotesize \textcolor{pink}{四国めたん:} では、サンプルコードを見ていきましょう

#include <stdio.h>
#include <string.h>

void main() {
  int a[] = {1, 2, 3, 4};
  memmove(&a[1], &a[0], (sizeof(int) * 3));
  printf("a[0]の値は%dです。\n", a[0]);
  printf("a[1]の値は%dです。\n", a[1]);
  printf("a[2]の値は%dです。\n", a[2]);
  printf("a[3]の値は%dです。\n", a[3]);
}

memmoveの結果

\footnotesize \textcolor{lime}{ずんだもん:} 内容がよく分からないのだ

\footnotesize \textcolor{pink}{四国めたん:} 配列の内部で要素のデータを移動するプログラムですわ

\footnotesize \textcolor{pink}{四国めたん:} 要素数4の配列でa[0]からa[2]の要素をa[1]からa[3]に移動していますわね

\footnotesize \textcolor{lime}{ずんだもん:} なるほど、memmoveでサイズを配列の要素数ではなく、sizeof(int) * 3としているのは、3つの要素を移動するためか...

\footnotesize \textcolor{pink}{四国めたん:} その通りですわ

\footnotesize \textcolor{lime}{ずんだもん:} 移動元のポインタは要素0のアドレスを、移動先のポインタを要素1のアドレスに指定しているのも、そのためか

\footnotesize \textcolor{pink}{四国めたん:} はい、結果として{1, 2, 3, 4}の配列が{1, 1, 2, 3}の配列になっていますわ

\footnotesize \textcolor{lime}{ずんだもん:} つまり、メモリ領域が重なっていても、正常にデータの移動が行われているということか...

\footnotesize \textcolor{pink}{四国めたん:} はい、その通りですわ

\footnotesize \textcolor{lime}{ずんだもん:} ところで、memmoveの方が使いやすいし、memcpyはいらないのではないのか?

\footnotesize \textcolor{pink}{四国めたん:} たしかにそうなのですが、一般的にmemcpyは制約がある分memmoveよりも高速に動作しますわ

\footnotesize \textcolor{lime}{ずんだもん:} 速度が必要なプログラムではmemcpyを優先した方がいいのか...

\footnotesize \textcolor{pink}{四国めたん:} まぁ、今時のコンピューターは高速に動作するので、特にこだわりがなければmemmoveを使う方がいいでしょう

memcmp

\footnotesize \textcolor{pink}{四国めたん:} 最後はmemcmpですわ

\footnotesize \textcolor{lime}{ずんだもん:} memcmp

\footnotesize \textcolor{pink}{四国めたん:} はい、指定したメモリ領域を比較して同じかどうかをチェックする関数ですわ

\footnotesize \textcolor{lime}{ずんだもん:} とりあえず便利なのか?

\footnotesize \textcolor{pink}{四国めたん:} 結構便利ですわね

\footnotesize \textcolor{pink}{四国めたん:} 形式としてはint memcmp(const void* ptr1, const void* ptr2, size_t num)となりますわ

\footnotesize \textcolor{lime}{ずんだもん:} 詳しく説明してほしいのだ

\footnotesize \textcolor{pink}{四国めたん:} わかりましたわ

\footnotesize \textcolor{pink}{四国めたん:} まず、比較するメモリ領域のポインタをptr1ptr2にセットしますわ

\footnotesize \textcolor{pink}{四国めたん:} そして、データの比較はnumにセットした値のバイト数分をバイト単位で行いますわ

\footnotesize \textcolor{lime}{ずんだもん:} バイト毎にデータを比較するのか?

\footnotesize \textcolor{pink}{四国めたん:} その通りですわ

\footnotesize \textcolor{pink}{四国めたん:} そして、比較結果が戻り値として返りますわ

\footnotesize \textcolor{lime}{ずんだもん:} 戻り値は int型 となっているが、どんな値が返ってくるのだ?

\footnotesize \textcolor{pink}{四国めたん:} 完全に一致していれば0が、一致していなければ0以外が返りますわ

\footnotesize \textcolor{pink}{四国めたん:} では、サンプルコードを見ていきましょう

#include <stdio.h>
#include <string.h>

void main() {
  int a[] = {1, 2, 3};
  int b[] = {1, 2, 3};

  if (memcmp(a, b, sizeof(a) {
    printf("aとbは同じではありません\n");
  } else {
    printf("aとbは同じです\n");
  }
}

memcmpの結果

\footnotesize \textcolor{lime}{ずんだもん:} 「aとbは同じです」と表示されたのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、同じ要素を持つ配列同士を比較したので当然ですわね

\footnotesize \textcolor{lime}{ずんだもん:} プログラムの説明をおねがいするのだ

\footnotesize \textcolor{pink}{四国めたん:} はい、今回はmemcmpif文の条件式として使っていますわ

\footnotesize \textcolor{lime}{ずんだもん:} 問題ないのか?

\footnotesize \textcolor{pink}{四国めたん:} はい、以前、0以外の整数は真偽値のtrueと同じとして扱われることをお話ししましたわね

\footnotesize \textcolor{lime}{ずんだもん:} 覚えているのだ

\footnotesize \textcolor{pink}{四国めたん:} memcmpは一致していなければ0以外、つまり真偽値のtrueを返すのでif文の条件式として使えるのですわ

\footnotesize \textcolor{lime}{ずんだもん:} memcmpで一致した場合は0、つまり真偽値のfalseが返るので、if文の条件式として使えるのか...

\footnotesize \textcolor{pink}{四国めたん:} その通りですわ

\footnotesize \textcolor{pink}{四国めたん:} ちなみに上記の配列の数値を変えて中身を一致しないようにすれば、結果は「aとbは同じではありません」と表示されますわ

#include <stdio.h>
#include <string.h>

void main() {
  int a[] = {1, 2, 3};
  int b[] = {1, 2, 0};

  if (memcmp(a, b, sizeof(a))) {
    printf("aとbは同じではありません\n");
  } else {
    printf("aとbは同じです\n");
  }
}

memcmpの結果2

\footnotesize \textcolor{lime}{ずんだもん:} 本当なのだ

まとめ

\footnotesize \textcolor{pink}{四国めたん:} お疲れさまでした

\footnotesize \textcolor{lime}{ずんだもん:} おつかれさまなのだ

\footnotesize \textcolor{pink}{四国めたん:} 以上で メモリ操作 についての説明は終わりですわ

Discussion