C言語におけるメモリ操作関数の仕組みと実装

メモリ操作関数の概要

C言語の標準ライブラリ <string.h> には、文字列操作だけでなく、メモリブロックを直接操作するための関数群が含まれています。これらの関数は、int型や構造体などの任意のデータ型に対してバイト単位で処理を行うため、汎用性が高い一方で、バイト単位の動作を正確に理解して使用する必要があります。

1. memset関数:メモリの塗りつぶし

memset は、指定したメモリ領域を特定の値(バイト単位)で埋めるために使用されます。主に構造体や配列の初期化に利用されます。

関数の仕様

void *memset(void *s, int c, size_t n);
  • s: セット対象のメモリブロックへのポインタ。
  • c: セットする値。int型として渡されますが、内部的にはunsigned charとして扱われます。
  • n: セットするバイト数。

使用例と注意点

整数型の配列を 0 以外で初期化する場合、期待通りにならないことがあります。これは memset が1バイトずつ値を書き込むためです。

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

int main() {
    int data[5];
    // すべてのバイトを 1 で埋める
    memset(data, 1, sizeof(data));
    
    // 1バイトが 01 なので、4バイトの int は 0x01010101 となり、
    // 10進数では 16843009 が出力される
    printf("%d\n", data[0]); 
    return 0;
}

独自の実装例

void* custom_memset(void* ptr, int value, size_t num) {
    unsigned char* p = (unsigned char*)ptr;
    while (num--) {
        *p++ = (unsigned char)value;
    }
    return ptr;
}

2. memcmp関数:メモリの比較

memcmp は、2つのメモリ領域をバイト単位で比較します。strcmp と異なり、途中に \0 があっても指定されたサイズ分まで比較を続けます。

関数の仕様

int memcmp(const void *s1, const void *s2, size_t n);
  • 戻り値が 0 の場合、両方の領域は等しいことを示します。
  • 0 以外の場合、最初に一致しなかったバイトの差を返します。

独自の実装例

int custom_memcmp(const void* ptr1, const void* ptr2, size_t n) {
    const unsigned char* s1 = (const unsigned char*)ptr1;
    const unsigned char* s2 = (const unsigned char*)ptr2;
    
    for (size_t i = 0; i < n; i++) {
        if (s1[i] != s2[i]) {
            return s1[i] - s2[i];
        }
    }
    return 0;
}

3. memcpy関数:メモリのコピー

memcpy は、あるメモリ領域から別の領域へ指定したバイト数分データをコピーします。コピー元とコピー先が重なっている場合の動作は定義されていません。

関数の仕様

void *memcpy(void *dest, const void *src, size_t n);

独自の実装例

void* custom_memcpy(void* destination, const void* source, size_t num) {
    unsigned char* dst = (unsigned char*)destination;
    const unsigned char* src = (const unsigned char*)source;
    
    while (num--) {
        *dst++ = *src++;
    }
    return destination;
}

4. memmove関数:重なりを考慮したコピー

memmovememcpy と同様にメモリをコピーしますが、コピー元とコピー先の領域が重なっている場合でも正しく動作することが保証されています。

重なりへの対処アルゴリズム

メモリが重なっている場合、コピーの方向に注意が必要です。

  • コピー先 (dest) がコピー元 (src) より前にある場合:先頭から順番にコピー(正方向)。
  • コピー先 (dest) がコピー元 (src) より後ろにある場合:末尾から逆順にコピー(逆方向)。これにより、上書きされる前にデータを退避できます。

独自の実装例

void* custom_memmove(void* dest, const void* src, size_t n) {
    unsigned char* d = (unsigned char*)dest;
    const unsigned char* s = (const unsigned char*)src;

    if (d < s) {
        // 前方からのコピー
        while (n--) {
            *d++ = *s++;
        }
    } else if (d > s) {
        // 後方からのコピー
        d += n;
        s += n;
        while (n--) {
            *(--d) = *(--s);
        }
    }
    return dest;
}

各関数の使い分け

関数 主な用途 特徴
memset 初期化 バイト単位で同一値を書き込む
memcmp 比較 バイナリデータの完全一致確認に最適
memcpy 高速コピー 領域が重ならないことが確実な場合に使用
memmove 安全なコピー 領域が重なる可能性がある場合に必須

タグ: C MemoryManagement CStandardLibrary LowLevelProgramming

5月18日 14:36 投稿