将多维数组作为 void * 传递给 extern "C" 函数

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

我的库中有一个 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));

我有一个 unsigned char * 是从 openCV 中定义的 class 接收到的。那个指针代表一个二维数据,但它不是,我必须将它与指针运算一起使用 (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 *,这可能会导致各种链接器错误。另外,我不知道 C bool 和 C++ bool 是否会在内存中转化为相同的东西(我希望如此)。

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

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

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

保证 T[N][M] 将包含 NxM 个连续的 T 类型对象阻碍了一些 otherwise-useful 优化;在 pre-standard 版本的 C 中,该保证的主要用途是它允许代码在某些情况下将存储视为 single-dimensional 数组,但在其他情况下将存储视为 multi-dimensional 数组。不幸的是,标准无法识别由内部数组的衰减形成的指针与通过直接或通过 void* 将外部数组转换为 inner-element 类型形成的指针之间的任何区别,即使它们对前者施加限制,这将阻碍后者的有用性。

在任何典型平台上,如果没有 whole-program 优化,ABI 会将指向 multi-dimensional 数组元素的指针视为等同于指向 single-dimensional 具有相同元素总数的数组,可以安全地将后者视为前者。但是,我不相信 C 或 C++ 标准中有任何内容会禁止 "optimizing" 的实现,例如:

// 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]++。我不认为该标准的作者打算邀请编译器做出这样的事情 "optimizations",但我不认为他们认为激进的优化器会将未能禁止此类事情解释为邀请。

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

为指针指定此类型也有助于提高可读性,因为可以使用 p[r * cols + c].

直接寻址矩阵单元格