为什么cudaMalloc()使用指向指针的指针?

45

举例来说,cudaMalloc((void**)&device_array, num_bytes);

这个问题曾经被人们问过,回答是“因为cudaMalloc返回一个错误码”,但我不理解——双重指针与返回错误码有什么关系?为什么不能用一个简单的指针完成工作?

如果我写成

cudaError_t catch_status;
catch_status = cudaMalloc((void**)&device_array, num_bytes);

错误代码将被放在catch_status中,返回一个简单的指向已分配GPU内存的指针就足够了,是吗?


15
这是一个C API,没有其他方法可以通过引用传递指针。 - talonmies
3个回答

75
在C语言中,数据可以通过传值或者模拟引用传递的方式(即通过指向数据的指针)传递到函数中。通过传值是一种单向方法,而通过指针则允许函数与其调用环境之间进行双向数据流动。
当通过函数参数列表将数据项传递给函数,并且期望函数修改原始数据项以使修改后的值显示在调用环境中时,采用正确的C方法是通过指针传递数据项。在C语言中,当我们通过指针传递数据时,我们获取要修改的项的地址,创建一个指针(在这种情况下可能是指向指针的指针),并将地址传递给函数。这样,函数就可以通过指针修改原始数据项,从而影响调用环境。

通常情况下,malloc 函数返回一个指针,我们可以在调用环境中使用赋值语句将该返回值分配给所需的指针。但是,在 cudaMalloc 函数中,CUDA 设计师选择使用返回值来携带错误状态而不是指针。因此,在调用环境中设置指针必须通过传递给函数的参数之一来进行,通过引用(即指针)传递。由于我们要设置的是一个指针值,所以我们必须取指针的地址(创建指向指针的指针),并将该地址传递给 cudaMalloc 函数。


1
啊,我明白了。我把返回值和按引用传递搞混了……谢谢! - rhyc
所以简单来说,你传递一个指向未来数组的指针a,因为你希望该方法更改该指针而不是通常情况下更改数组。 - Íhor Mé

10

补充Robert的回答,但首先重申一下,这是一个C API,意味着它不支持引用(reference),这将允许您在函数内修改指针的值(而不仅仅是其所指向的内容)。Robert Crovella的回答解释了这一点。同时请注意,它需要使用void,因为C也不支持函数重载。

此外,在C++程序中使用C API时(但您没有说明这一点),通常会将这样的函数封装在模板中。例如:

template<typename T>
cudaError_t cudaAlloc(T*& d_p, size_t elements)
{
    return cudaMalloc((void**)&d_p, elements * sizeof(T));
}

以下是与上述cudaAlloc函数调用方式的两个不同之处:
  1. 在调用时直接传递设备指针,不使用取地址运算符(&),也不需要转换为void类型。
  2. 第二个参数elements现在是元素的数量而不是字节数。使用sizeof运算符可以方便地实现这一点。这样指定元素而不必担心字节可能更加直观。
例如:
float *d = nullptr;  // floats, 4 bytes per elements
size_t N = 100;      // 100 elements

cudaError_t err = cudaAlloc(d,N);      // modifies d, input is not bytes

if (err != cudaSuccess)
    std::cerr << "Unable to allocate device memory" << std::endl;

5

我猜cudaMalloc函数的签名可以通过一个例子更好地解释。它基本上是通过指向缓冲区的指针(指向指针)来分配缓冲区,就像以下方法:

int cudaMalloc(void **memory, size_t size)
{
    int errorCode = 0;

    *memory = new char[size];

    return errorCode;
}

正如你所看到的,该方法接受一个指向指针的memory,并将新分配的内存保存在其中。然后它返回错误代码(在此情况下为整数,但实际上是枚举类型)。

cudaMalloc函数也可以设计如下:

void * cudaMalloc(size_t size, int * errorCode = nullptr)
{
    if(errorCode)
        errorCode = 0;

    char *memory = new char[size];

    return memory;
}

在第二种情况下,错误代码通过指针隐式设置为null(对于那些不关心错误代码的人)。然后返回分配的内存。
第一种方法可以直接使用当前的cudaMalloc。
float *p;
int errorCode;
errorCode = cudaMalloc((void**)&p, sizeof(float));

第二个可以按以下方式使用:
float *p;
int errorCode;
p = (float *) cudaMalloc(sizeof(float), &errorCode);

这两种方法在功能上是等效的,尽管它们有不同的签名,但cuda团队决定采用第一种方法,返回错误代码并通过指针分配内存,而大多数人认为第二种方法可能是更好的选择。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接