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

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


# Linux / macOS の場合

基本手順

1. [open](https://manpages.debian.org/bullseye/manpages-ja-dev/open.2.ja.html) でファイルを開く
1. [fstat](https://manpages.debian.org/testing/manpages-ja-dev/fstatat64.2.ja.html) でファイル サイズを取得
1. [mmap](https://manpages.debian.org/buster/manpages-ja-dev/mmap.2.ja.html) でメモリ上に写像
1. メモリ上に出現したファイル(データ)にアクセスなど...
1. [munmap](https://manpages.debian.org/jessie/manpages-ja-dev/munmap.2.ja.html) でメモリ上から消去
1. [close](https://manpages.debian.org/testing/manpages-ja-dev/close.2.ja.html) でファイルを閉じる


# Windows の場合

基本手順

1. [CreateFileA](https://learn.microsoft.com/ja-jp/windows/win32/api/fileapi/nf-fileapi-createfilea) / [CreateFileW](https://learn.microsoft.com/ja-jp/windows/win32/api/fileapi/nf-fileapi-createfilew) でファイル ハンドルを取得
1. [GetFileSizeEx](https://learn.microsoft.com/ja-jp/windows/win32/api/fileapi/nf-fileapi-getfilesizeex) でファイル サイズを取得
1. [CreateFileMappingA](https://learn.microsoft.com/ja-jp/windows/win32/api/winbase/nf-winbase-createfilemappinga) / [CreateFileMappingW](https://learn.microsoft.com/ja-jp/windows/win32/api/memoryapi/nf-memoryapi-createfilemappingw) でマッピング用ハンドルを取得
1. [MapViewOfFile](https://learn.microsoft.com/ja-jp/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffile) でメモリ上に写像
1. メモリ上に出現したファイル(データ)にアクセスなど...
1. [UnmapViewOfFile](https://learn.microsoft.com/ja-jp/windows/win32/api/memoryapi/nf-memoryapi-unmapviewoffile) でメモリ上から消去
1. [CloseHandle](https://learn.microsoft.com/ja-jp/windows/win32/api/handleapi/nf-handleapi-closehandle) でマッピング用ハンドルとファイル ハンドルを閉じる

https://learn.microsoft.com/ja-jp/windows/win32/fileio/creating--deleting--and-maintaining-files

https://learn.microsoft.com/ja-jp/windows/win32/memory/memory-management-functions


# サンプルプログラム

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

```c++: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:
```

```console:実行結果
$ 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:.
```