0
1

備忘録:ファイル全体をメモリ上にマッピング

Last updated at Posted at 2024-06-05

久しぶりに書いたらかなり忘れていたので、ここに残しておく。

Linux / macOS の場合

基本手順

  1. open でファイルを開く
  2. fstat でファイル サイズを取得
  3. mmap でメモリ上に写像
  4. メモリ上に出現したファイル(データ)にアクセスなど...
  5. munmap でメモリ上から消去
  6. close でファイルを閉じる

Windows の場合

基本手順

  1. CreateFileA / CreateFileW でファイル ハンドルを取得
  2. GetFileSizeEx でファイル サイズを取得
  3. CreateFileMappingA / CreateFileMappingW でマッピング用ハンドルを取得
  4. MapViewOfFile でメモリ上に写像
  5. メモリ上に出現したファイル(データ)にアクセスなど...
  6. UnmapViewOfFile でメモリ上から消去
  7. CloseHandle でマッピング用ハンドルとファイル ハンドルを閉じる

サンプルプログラム

読み取り専用で処理しています。

sample.cpp
#if !defined(_WINDOWS) && (_WIN32 || _WIN64)
#define _WINDOWS  1
#endif

#if _WINDOWS
#include <Windows.h>
#else
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#endif

#include <algorithm>
#include <iostream>

/*
 * 読み取り専用でファイル全体をメモリ上に写像
 */
struct mapped_file
{
#if _WINDOWS
    HANDLE m_hFile;
    HANDLE m_hMap;
#else
    int fd;
#endif

    bool opened;
    void *addr;
    size_t size;

    // コンストラクタ.
    mapped_file(const char *path)
    {
#if _WINDOWS
        m_hFile = INVALID_HANDLE_VALUE;
        m_hMap = NULL;
#else
        fd = -1;
#endif
        opened = false;
        addr = NULL;
        size = 0;

        opened = open(path);
    }

    // デストラクタ.
    ~mapped_file()
    {
        close();
    }

    // ファイルを開いて、全体をメモリ上に写像する.
    bool open(const char* path)
    {
        close();

#if _WINDOWS

        // ファイルを開く.
        m_hFile = ::CreateFileA(path,
                                GENERIC_READ,
                                FILE_SHARE_READ,
                                NULL,
                                OPEN_EXISTING,
                                FILE_ATTRIBUTE_NORMAL,
                                NULL);
        if (m_hFile == INVALID_HANDLE_VALUE)
            return false;

        // ファイル サイズを得る.
        LARGE_INTEGER fsize;
        if (!::GetFileSizeEx(m_hFile, &fsize))
        {
            close();
            return false;
        }

        // ファイル サイズが 0 なら終了.
        size = size_t((std::uint64_t(fsize.HighPart) << 32) + fsize.LowPart);
        if (size == 0)
        {
            opened = true;
            addr = NULL;
            return true;
        }

        // ファイル写像オブジェクトを作る.
        m_hMap = ::CreateFileMappingA(m_hFile,
                                      NULL,
                                      PAGE_READONLY,
                                      fsize.HighPart,
                                      fsize.LowPart,
                                      NULL);
        if (m_hMap == NULL)
        {
            close();
            return false;
        }

        // メモリ上にファイル全体を写像.
        addr = ::MapViewOfFile(m_hMap, FILE_MAP_READ, 0, 0, size);
        if (addr == NULL)
        {
            close();
            return false;
        }

        opened = true;
        return true;

#else

        // ファイルを開く.
        fd = ::open(path, O_RDONLY);
        if (fd < 0)
            return false;

        // ファイル サイズを得る.
        struct stat stbuf;
        if (::fstat(fd, &stbuf) < 0)
        {
            close();
            return false;
        }

        // ファイル サイズが 0 なら終了.
        size = size_t(stbuf.st_size);
        if (size == 0)
        {
            opened = true;
            return true;
        }

        // メモリ上にファイル全体を写像.
        addr = ::mmap(NULL,
                      size,
                      PROT_READ,
                      MAP_FILE | MAP_PRIVATE,
                      fd,
                      0);
        if (addr == MAP_FAILED)
        {
            close();
            return false;
        }

        opened = true;
        return true;

#endif
    }

    // 破棄.
    void close()
    {
#if _WINDOWS

        if (size > 0 && !::UnmapViewOfFile(addr))
            std::cerr << "error: UnmapViewOfFile" << std::endl;
        if (m_hMap != NULL && !::CloseHandle(m_hMap))
            std::cerr << "error: CloseHandle(m_hMap)" << std::endl;
        if (m_hFile != INVALID_HANDLE_VALUE && !::CloseHandle(m_hFile))
            std::cerr << "error: CloseHandle(m_hFile)" << std::endl;

        m_hFile = INVALID_HANDLE_VALUE;
        m_hMap = NULL;

#else

        if (size > 0 && ::munmap(addr, size) < 0)
            std::cerr << "error: munmap" << std::endl;
        if (fd >= 0 && ::close(fd) < 0)
            std::cerr << "error: close(" << fd << ")" << std::endl;
        fd = -1;

#endif

        opened = false;
        addr = NULL;
        size = 0;
    }
};

/*
 *
 */

int main(int argc, char** argv)
{
    if (argc != 2)
    {
        std::cout << "Usage: " << argv[0] << " FILE" << std::endl;
        return 1;
    }

    static const char hex[] = "0123456789abcdef";

    const char* path = argv[1];
    mapped_file file(path);

    if (!file.opened)
    {
        std::cout << "open error: " << path << std::endl;
        return 2;
    }

    // バイナリ出力.

    std::cout << "File: " << path << std::endl
              << "Size: "
              << "0x" << std::hex << file.size << std::dec
              << " (" << file.size << ')' << std::endl;

    int aw = 0;
    for (size_t s = file.size - 1; s > 0; s >>= 4, aw += 4);
    aw = std::max<int>(aw, 16) - 4;

    const char* data = (const char*)file.addr;

    for (size_t y = 0; y < file.size; y += 16)
    {
        size_t r = file.size - y;
        size_t n = (r < 16) ? r : 16;

        for (int b = aw; b >= 0; b -= 4)
            std::cout << hex[(y >> b) & 15];
        std::cout << ':';

        for (size_t x = 0; x < n; ++x)
        {
            char d = data[y + x];

            std::cout << ' ';
            std::cout << hex[(d >> 4) & 15];
            std::cout << hex[(d >> 0) & 15];
        }
        for (size_t x = n; x < 16; ++x)
            std::cout << "   ";

        std::cout << "  ";

        for (size_t x = 0; x < n; ++x)
        {
            char d = data[y + x];
            std::cout << ((0x20 <= d && d <= 0x7e) ? d : '.');
        }

        std::cout << std::endl;
    }

    return 0;
}

// Local Variables:
// c-basic-offset: 4
// indent-tabs-mode: nil
// tab-width: 4
// End:
実行結果
$ clang++ -O sample.cpp && ./a.out sample.cpp
File: sample.cpp
Size: 0x16e0 (5856)
0000: 23 69 66 20 21 64 65 66 69 6e 65 64 28 5f 57 49  #if !defined(_WI
0010: 4e 44 4f 57 53 29 20 26 26 20 28 5f 57 49 4e 33  NDOWS) && (_WIN3
0020: 32 20 7c 7c 20 5f 57 49 4e 36 34 29 0a 23 64 65  2 || _WIN64).#de
0030: 66 69 6e 65 20 5f 57 49 4e 44 4f 57 53 20 20 31  fine _WINDOWS  1
~~~(略)~~~
16c0: 64 65 3a 20 6e 69 6c 0a 2f 2f 20 74 61 62 2d 77  de: nil.// tab-w
16d0: 69 64 74 68 3a 20 34 0a 2f 2f 20 45 6e 64 3a 0a  idth: 4.// End:.
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1