将多维数组传递给外部"C"函数为 void *

Pass multidimensional array to extern "C" function as void *

本文关键字:函数 void 外部 数组      更新时间:2023-10-16

我的库中有一个 C 函数,可以很好地处理多维数组:

void    alx_local_maxima_u8 (ptrdiff_t rows, ptrdiff_t cols,
const uint8_t arr_in[static restrict rows][static cols],
bool arr_out[static restrict rows][static cols])
__attribute__((nonnull));

我有一个从 openCV 中定义的class收到的unsigned char *。 该指针表示二维数据,但事实并非如此,我必须将其与指针算术(unsigned char *img_pix = img->data + i*img->step + j;)一起使用,我不是特别喜欢。

我创建了一个图像大小相同的bool数组(这是一个真实的数组,所以我可以使用数组符号)来存储函数的结果。

我可以编写一个几乎完全相同的alx_local_maxima_u8()副本,只使用指针和指针算术,但如果可以的话,我希望能够重用它。

编写一个以这种方式使用void *的原型只是为了愚弄C++是否安全?

extern "C"
{
[[gnu::nonnull]]
void    alx_local_maxima_u8 (ptrdiff_t rows, ptrdiff_t cols,
const void *arr_in,
void *arr_out);
}

理论上void *可以保存 C 将接收的任何指针,而 C 将无法访问不属于这些指针的任何数据,所以我看到的唯一问题是将unsigned char *别名为uint8_t *[],并在预期uint8_t *[]的地方传递void *,这可能会导致所有类型的链接器错误。 另外,我不知道 Cbool和 C++bool是否会在内存中转换为相同的东西(我希望如此)。

也许我应该用 C 编写一个包装器来接收void *并将它们传递给实际函数,这样我就不需要愚弄C++。

性能是一个问题,但我使用-flto,所以任何包装器都可能会在链接器中消失。

我在启用了POSIX的Linux中使用GCC(-std=gnu++17)。

保证 T[N][M] 将包含 T 类型的 NxM 连续对象阻碍了一些其他有用的优化;在 C 的试行标准版本中,这种保证的主要用途是它允许代码在某些上下文中将存储视为一维数组,但在其他上下文中将存储视为多维数组。 不幸的是,标准没有认识到由内部数组衰减形成的指针与直接或通过void*将外部数组转换为内部元素类型形成的指针之间的任何区别,即使它们对前者施加了限制,这会阻碍后者的有用性。

在任何典型的平台上,在没有全程序优化的情况下,ABI 会将指向多维数组元素的指针视为等效于指向具有相同元素总数的一维数组元素的指针,从而可以安全地将后者视为前者。 但是,我不相信 C 或 C++ 标准中有任何内容会禁止实现"优化"以下内容:

// In first compilation unit
void inc_element(void*p, int r, int c, int stride)
{
int *ip = (int*)p;
ip[r*stride+c]++;
}
// In second compilation unit
int array[5][5];
void inc_element(void*p, int r, int c, int stride);
int test(int i)
{
if (array[1][0])
inc_element(array, i, 0, 5);
return array[1][0];
}

通过将inc_element调用替换为array[0][i*5]++,这反过来又可以优化为array[0][0]++。 我不认为标准的作者打算邀请编译器进行这样的"优化",但我不认为激进的优化者会将未能禁止此类事情解释为邀请。

将数组指针作为const void *传递应该不会导致任何问题,但请注意,bool在 C 和 C++ 中可能具有不同的表示形式。对数组基类型使用更显式的类型(如unsigned char)会更安全。

为指针指定此类型也有助于提高可读性,因为可以使用p[r * cols + c]直接对矩阵单元格进行寻址。