C++中复制cv::Mat图像到指定地址时程序崩溃的原因及解决办法
嘿,我来帮你拆解下这段代码崩溃的核心问题,以及对应的解决办法:
首先先把你的代码贴出来方便分析:
int main() { cv::Mat inImg = cv::imread("M6_traffic.jpg"); //Data point copy unsigned char * pData = (unsigned char *)inImg.data; unsigned char * Dest = (unsigned char *)0x0f000000;; int width = inImg.rows; int height = inImg.cols; //data copy using memcpy function memcpy(Dest, pData, sizeof(unsigned char)*width*height*3); }
为什么会崩溃?
你这段代码崩溃的关键原因在于直接硬编码了内存地址0x0f000000作为目标地址Dest。
现代操作系统(不管是Windows、Linux还是macOS)都有严格的虚拟内存保护机制:每个进程只能访问自己被分配到的虚拟内存空间,随便写一个地址大概率是未分配的内存区域,或者属于系统内核/其他进程的受保护内存,你的程序完全没有读写权限。一旦执行memcpy往这个地址写数据,操作系统就会触发内存访问错误(比如Linux下的段错误、Windows下的访问违规),直接把程序终止掉。
另外还有个小细节:你把inImg.rows赋值给了width,inImg.cols赋值给了height——其实rows是图像的高度,cols才是宽度,虽然乘法交换律不影响总字节数,但逻辑上是错的,建议修正。
解决方法
根据你的实际需求,分两种情况给出方案:
情况1:只是需要一块普通内存存储图像数据
如果你的需求只是把图像数据复制到一块可用的内存里,完全不需要硬编码地址,用标准的内存分配方式就好:
方案A:用new/delete手动管理堆内存
#include <opencv2/opencv.hpp> #include <cstring> int main() { cv::Mat inImg = cv::imread("M6_traffic.jpg"); // 先判断图像是否加载成功,避免空指针问题 if (inImg.empty()) { printf("图像加载失败!\n"); return -1; } unsigned char* pData = inImg.data; // 用channels()获取通道数,比硬写3更通用(比如灰度图是1通道) int totalBytes = inImg.rows * inImg.cols * inImg.channels(); // 分配合法的堆内存 unsigned char* Dest = new unsigned char[totalBytes]; // 复制数据 memcpy(Dest, pData, totalBytes); // 这里可以使用Dest中的图像数据 // 记得释放内存,避免内存泄漏 delete[] Dest; return 0; }
方案B:用std::vector自动管理内存(更安全)
用std::vector的话,不需要手动释放内存,它会自动在析构时清理:
#include <opencv2/opencv.hpp> #include <cstring> #include <vector> int main() { cv::Mat inImg = cv::imread("M6_traffic.jpg"); if (inImg.empty()) { printf("图像加载失败!\n"); return -1; } int totalBytes = inImg.rows * inImg.cols * inImg.channels(); // 直接创建对应大小的vector std::vector<unsigned char> Dest(totalBytes); // 复制数据到vector的内存中 memcpy(Dest.data(), inImg.data, totalBytes); // 通过Dest.data()访问图像数据即可,无需手动释放 return 0; }
情况2:确实需要写入特定的物理内存地址(比如嵌入式硬件场景)
如果0x0f000000是硬件设备的物理地址(比如嵌入式开发中需要往显存、外设内存写数据),那在现代操作系统下不能直接访问物理地址,需要通过内存映射把物理地址映射到进程的虚拟地址空间,获得合法的虚拟地址后再操作。
以Linux为例,大致实现代码如下(需要root权限):
#include <opencv2/opencv.hpp> #include <cstring> #include <fcntl.h> #include <sys/mman.h> #include <unistd.h> #include <stdio.h> int main() { cv::Mat inImg = cv::imread("M6_traffic.jpg"); if (inImg.empty()) { printf("图像加载失败!\n"); return -1; } int totalBytes = inImg.rows * inImg.cols * inImg.channels(); // 目标物理地址 off_t physicalAddr = 0x0f000000; // 打开/dev/mem设备文件 int fd = open("/dev/mem", O_RDWR | O_SYNC); if (fd == -1) { perror("打开/dev/mem失败"); return -1; } // 将物理地址映射到进程的虚拟地址空间 unsigned char* Dest = (unsigned char*)mmap( NULL, // 让系统自动选择虚拟地址 totalBytes, // 映射的大小 PROT_READ | PROT_WRITE, // 读写权限 MAP_SHARED, // 共享映射 fd, // 设备文件描述符 physicalAddr // 要映射的物理地址 ); if (Dest == MAP_FAILED) { perror("内存映射失败"); close(fd); return -1; } // 复制图像数据到映射后的地址 memcpy(Dest, inImg.data, totalBytes); // 这里可以使用Dest中的数据 // 解除映射并关闭文件 munmap(Dest, totalBytes); close(fd); return 0; }
如果是Windows系统,需要用CreateFileMapping和MapViewOfFile等函数来实现类似的内存映射功能,逻辑是一样的:先把物理地址/设备内存映射到进程虚拟空间,再进行读写。
内容的提问来源于stack exchange,提问作者c.garry

