高度なメモリの扱い

このエントリーをはてなブックマークに追加

高度なメモリの扱い

前回はmallocやxmalloc等の基本的なメモリ周りの扱いについて解説しました。
今回はさらに高度なメモリ周りの解説をします。

allcaで楽をしよう!

最近流行りの言語にはGCがあります。C言語等を触っていると本当に羨ましくなります。GCを自分で実装すれば良いのでしょうが本当に難しくなかなか実装は出来ないでしょう。
だからといって、特定の関数内だけででmalloc(3)したいという場合には非常にめんどくさいですね。free(3)をするのもついつい忘れがちです。

そこで、alloca(3)の登場です。

#include <alloca.h>
void *alloca(size_t size);

alloca()  関数は、 size バイトの領域を呼出元のスタック・フレームに割り付ける。
この一時的な領域は、 alloca() を呼び出した関数が呼出元に返るときに自動的に解放される。

何を言いたいのかが分かり辛いのですが、関数を抜ければ自動的にメモリが開放される、といっているのです。簡易GCですね。
これは使わない手はないです。
#include <stdio.h>
#include <alloca.h>

void alloca_test(void)
{
    char *p;

    //必要な領域を取得
    //alloca_test()を抜けた時点で開放される。free()は必要ない!!
    p = alloca(128);

    //pへ何か操作
    snprintf(p, 128, "abcdefg");
    printf("%s\n", p);
}

int main(void)
{
    alloca_test();

    return 0;
}

前回と比べると非常に簡単になりましたね。
ただし、問題点もあります。alloca(3)で取得したメモリはスタックに割り当てられますので、スタックオーバーフローが起きた場合にはバグの原因にもなります。
小さめのちょっとした関数で利用するのが良いでしょう。


リソースをメモリにマッピングするmmap

使い所は非常に難しいのですが、Unixにはリソースをメモリにマッピングする機能としてmmmap(2)というのがあります。
説明も非常にしづらいのですが、例えばファイルをメモリにマッピングして、ファイルをメモリと同様に扱い読み書きを出来るようにすることができます。状況にもよりますが、処理が高速化する場合もあります。


まずは簡単な例としてファイルのコピーを行うプログラムです。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(void)
{
    int fd_input, fd_output;
    ssize_t size;
    char buf[1024];

    fd_input = open("input", O_RDONLY);
    fd_output = open("output", O_WRONLY|O_CREAT, 0755);

    while(1) {
            size = read(fd_input, buf, 1024);
            write(fd_output, buf, size);
            if (size < 1024) {
                    break;
            }
    }

    close(fd_input);
    close(fd_output);

    return 0;
}

シンプルなプログラムです。特になにも解説は必要ないでしょう。

ではこれをmmap(2)を使用して書き換えてみましょう。
先に注意点を。実行するときは、"input"と同じサイズの"output"ファイルを用意してください。
"output"のファイルサイズが小さい場合、SIBGUSが発生します。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>

int main(void)
{
    int fd_input, fd_output;
    off_t pos;
    char *m_input, *m_output;

    fd_input = open("input", O_RDONLY);
    fd_output = open("output", O_RDWR);
  
    pos = lseek(fd_input, 0, SEEK_END);
    lseek(fd_input, 0, SEEK_SET);

    m_input = mmap(NULL, pos, PROT_READ, MAP_SHARED, fd_input, 0);
    m_output = mmap(NULL, pos, PROT_WRITE, MAP_SHARED, fd_output, 0);

    memcpy(m_output, m_input, pos);

    //書き込み
    msync(m_output, pos, 0);

    //使ったら終了しましょう
    munmap(m_input, pos);
    munmap(m_output, pos);

    return 0;
}

mmap(2)を使うメリットはいくつかあります。
1つは、このようにファイル操作がメモリ操作で置き換えられます。これによりファイル操作が非常に簡単になります。
またmmap(2)を使うと、使用しているシステムコールが少なくなる傾向があります。システムコールはOSが提供している機能ですので当然実行速度が早くなります。
もう1つはメモリ確保が難しい場合でも対応できます。
メモリマッピングと聞くと難しそうですが、意外と簡単に出来ることが分かったと思います。ただし、ある程度使いこなすにはそれなりに技術も必要です。
場合によってはシステムを壊してしまうことも可能ですので、まずは簡単なテストプログラムを作って安全性の確認をしてからにしてください。実際過去に仕事でシステムを壊した例を見たこともあります。
さらに、mloc(2)、mremap(2)、mprotect(2)等の関連した関数もありますので参考にしてください。


まとめ

まだまだメモリについては奥が深いのですが、一旦これで終了とします。
mmap(2)は非常に強力で使い所が難しいですが、是非マスターしてください。
次は、外部ツールに頼らず自前でデバッグの助けになる機能の解説をしていきます。

OSが提供する機能は様々あります。こういった書籍を参考にしてください