Windows用CreateFileMapping+MapViewOfFile、Linux用shm_open+mmap实现共享内存,二者均需注意大小设置、错误处理、同步机制及跨平台兼容性,且共享内存中不可存放std::string等非平凡类型。

Windows 下用 CreateFileMapping 和 MapViewOfFile 创建共享内存
Windows 原生共享内存依赖内存映射文件(Memory-Mapped File),即使不关联磁盘文件,也能通过 INVALID_HANDLE_VALUE 创建纯内存段。关键不是“文件”,而是“映射对象”。
常见错误是传错 dwMaximumSizeHigh/dwMaximumSizeLow —— 它们合起来是 64 位大小,但多数场景用不到高位,直接设为 0 即可;若实际大小超 4GB,才需拆分赋值。
-
CreateFileMapping返回NULL时,务必调用GetLastError()判断原因:权限不足(ERROR_ACCESS_DENIED)、名字冲突(ERROR_ALREADY_EXISTS)或内存不足(ERROR_NOT_ENOUGH_MEMORY)都得区分处理 - 映射视图前必须确保
hMap有效,且MapViewOfFile的dwNumberOfBytesToMap不能超过创建时声明的大小 - 多个进程访问同一块共享内存时,**没有内置同步机制**,必须额外配
CreateEvent或CreateMutex控制读写顺序
HANDLE hMap = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, 1024, TEXT("MySharedMem"));
if (hMap == NULL) {
// 处理错误
}
LPVOID pMem = MapViewOfFile(hMap, FILE_MAP_ALL_ACCESS, 0, 0, 1024);
if (pMem == NULL) {
CloseHandle(hMap);
}Linux 下用 shm_open + mmap 创建 POSIX 共享内存
Linux 推荐用 POSIX 标准接口:shm_open 创建/打开一个共享内存对象(本质是 /dev/shm/ 下的虚拟文件),再用 mmap 映射进地址空间。它比 sysv shm(shmget 系列)更现代、路径可控、权限清晰。
容易忽略的是 shm_open 的 oflag 参数:跨进程通信必须带 O_RDWR,首次创建还要加 O_CREAT;同时 mode(如 0666)决定其他用户能否打开——若不设,可能被 umask 截断导致权限不足。
立即学习“C++免费学习笔记(深入)”;
-
shm_open返回的 fd 必须在mmap后close(),否则资源泄漏(fd 不释放,但映射仍有效) -
mmap的length必须与ftruncate设置的大小一致,否则读写越界或SEGV - 进程退出时不自动销毁共享内存,需显式调用
shm_unlink(仅删除名字,已映射的仍可用)
int fd = shm_open("/my_shm", O_CREAT | O_RDWR, 0666);
ftruncate(fd, 1024);
void* ptr = mmap(nullptr, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd); // fd 可关,ptr 仍有效跨平台封装要注意的兼容性陷阱
Windows 和 Linux 的共享内存 API 差异大,强行抽象成统一接口容易踩坑。最现实的做法是按平台条件编译,或只封装“创建/映射/销毁”三步,把同步逻辑完全交给上层。
典型陷阱包括:
- 名字格式:Windows 支持任意字符串(如
"MyMem"),Linux 的shm_open要求以/开头且不能含其它斜杠("/my_mem"合法,"/a/b"非法) - 大小限制:Windows 单个映射上限约 2GB(32 位进程),Linux 默认
/dev/shm大小常为 64MB,可通过mount -o remount,size=2G /dev/shm调整 - 生命周期管理:Linux 的
shm_unlink类似于 Windows 的CloseHandle+ 名字注销,但 Windows 没有等价的“仅删名不关句柄”操作
为什么共享内存里不能放 std::string 或虚函数对象
共享内存本质是一段裸字节区域,所有数据必须是 trivially copyable,且地址无关。一旦结构体里包含 std::string、std::vector、指针、虚表指针或非 POD 成员,就绝不能直接 memcpy 进去。
例如:struct Data { std::string name; int id; }; —— name 内部指针指向堆内存,其他进程无法访问;虚函数对象的 vptr 指向本进程的虚表,跨进程失效。
- 正确做法是用固定长度 C 风格数组(
char name[64])、手动序列化、或使用boost::interprocess提供的容器(如boost::interprocess::basic_string) - 若必须动态结构,建议只存偏移量+长度,把真实数据也布局在同一块共享内存中(即“内存池 + 相对指针”模式)
- 所有字段必须显式对齐(
alignas(8)),避免不同编译器填充差异导致读写错位
共享内存本身不难创建,难的是让两个独立进程安全、稳定、一致地解释同一片内存里的比特。类型布局、生命周期、同步粒度,这三样漏掉任何一环,都会在某个并发时机突然崩掉。











