如何在CUDA C++中实现从__host__分配__device__堆内存的自定义类型?
问题分析与修正方案
你的代码核心问题在于将host端的对象指针直接传递给kernel,device无法访问host内存;同时存在内存管理混用、move构造不完整等问题。以下是分步修正:
核心错误点梳理
- Host对象指针直接传入Kernel:
&arr是host栈上的对象地址,device线程无法直接访问host内存,导致访问arr->size()时出错。 - Move构造函数遗漏成员:只转移了
arr指针,未处理_size,会导致原对象析构时重复释放内存。 - 内存分配方式混用:
resize中用delete[]释放cudaMalloc分配的内存,new和cudaMalloc属于不同内存管理域,不能混用。 - 索引边界判断错误:
i > *_size应该改为i >= *_size,因为数组索引从0开始,最大有效索引为size-1。
修正后的完整代码
#include <iostream> #include <algorithm> // 引入std::min/std::max template <typename T> class Array{ T *arr; size_t* _size; public: // Move构造函数:完善_size的转移 __device__ __host__ Array(Array&& other){ arr = other.arr; _size = other._size; other.arr = nullptr; other._size = nullptr; } // Host端构造函数:从host数组初始化device内存 __host__ Array(T* other_arr, size_t size){ cudaMalloc(&_size, sizeof(size_t)); cudaMalloc(&arr, sizeof(T) * (size + 1)); // +1用于越界返回 cudaMemcpy(_size, &size, sizeof(size_t), cudaMemcpyHostToDevice); cudaMemcpy(arr, other_arr, sizeof(T) * size, cudaMemcpyHostToDevice); } // 析构函数:只在host端释放,确保指针不为空时再释放 __host__ ~Array(){ if (_size) cudaFree(_size); if (arr) cudaFree(arr); } // 禁用拷贝构造和拷贝赋值,避免浅拷贝导致重复释放 Array(const Array&) = delete; Array& operator=(const Array&) = delete; Array& operator=(Array&&) = delete; // 写访问:修正边界判断 __device__ T &operator[](size_t i){ if (i >= *_size) return arr[*_size]; // 返回预留的越界位置 return arr[i]; } // 读访问:修正边界判断 __device__ const T &operator[](size_t i) const { if (i >= *_size) return arr[*_size]; return arr[i]; } // 获取大小 __device__ size_t size() const { return *_size; } // 修正resize函数:使用cudaMalloc/cudaFree管理内存 __device__ void resize(size_t n){ if (arr) cudaFree(arr); *_size = n; cudaMalloc(&arr, sizeof(T) * (n + 1)); } }; // 最小元素函数 template<typename T> __device__ T min(const Array<T>& a){ T m = a[0]; for(size_t i = 1; i < a.size(); i++) m = std::min(m, a[i]); return m; } // 最大元素函数 template<typename T> __device__ T max(const Array<T>& a){ T m = a[0]; for(size_t i = 1; i < a.size(); i++) m = std::max(m, a[i]); return m; } // Kernel:接收device端的Array指针 __global__ void k_sum_array(Array<int>* arr, int* s){ *s = 0; for(size_t i = 0; i < arr->size(); i++) *s += (*arr)[i]; // 简化调用,等价于arr->operator[](i) } int main(){ int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; size_t size = 10; // 1. 在host创建Array对象 Array<int> host_arr(a, size); // 2. 分配device端的Array内存,并将host对象拷贝到device Array<int>* dev_arr; cudaMalloc(&dev_arr, sizeof(Array<int>)); cudaMemcpy(dev_arr, &host_arr, sizeof(Array<int>), cudaMemcpyHostToDevice); // 3. 分配device端的结果变量 int* dev_s; cudaMalloc(&dev_s, sizeof(int)); // 4. 启动Kernel,传递device端的Array指针 k_sum_array<<<1, 1>>>(dev_arr, dev_s); cudaDeviceSynchronize(); // 等待Kernel执行完成,方便排查错误 // 5. 将结果拷贝回host int host_s; cudaMemcpy(&host_s, dev_s, sizeof(int), cudaMemcpyDeviceToHost); std::cout << host_s << std::endl; // 预期输出55 // 6. 释放内存:dev_arr仅释放对象本身内存,成员内存由host_arr析构处理 cudaFree(dev_arr); cudaFree(dev_s); return 0; }
关键修改说明
- 对象拷贝到Device:在main中,用
cudaMalloc分配device端的Array对象内存,再将host的Array对象拷贝过去。因为Array的成员是device指针(数值型),拷贝到device后依然指向正确的device内存区域。 - 完善Move构造:同时转移
arr和_size,并将原对象的指针置空,避免析构时重复释放。 - 禁用拷贝操作:默认的浅拷贝会导致多个对象持有同一device指针,析构时重复释放,因此禁用拷贝构造和赋值运算符。
- 修正Resize逻辑:使用
cudaFree和cudaMalloc管理内存,和构造函数的分配方式保持一致。 - 添加同步操作:
cudaDeviceSynchronize()确保Kernel执行完成后再拷贝结果,避免因异步执行导致的结果未就绪问题。 - 修正索引判断:将
i > *_size改为i >= *_size,符合数组索引的边界规则。
注意事项
- 若需要在device端创建Array对象,可添加
__device__构造函数,但需确保内存分配逻辑适配device环境。 - 析构函数仅在host端执行,
cudaFree不能在device端调用(除非使用CUDA动态并行,需额外配置),因此device端的Array对象不需要手动析构,其成员内存由host端对象负责释放。 - 编译时需指定CUDA架构,例如
nvcc -arch=sm_75 your_code.cu -o your_code(根据你的GPU架构调整sm版本)。
内容的提问来源于stack exchange,提问作者MaxWell
相关产品推荐
相关产品推荐

