首页 > 解决方案 > 将多维数组作为 void * 传递给外部“C”函数

问题描述

我的库中有一个 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 *我从classopenCV 中定义的接收到的。该指针表示二维数据,但它不是,我必须将它与指针算术 ( unsigned char *img_pix = img->data + i*img->step + j;) 一起使用,我不是特别喜欢。

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

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

编写一个void *以这种方式使用 a 的原型来愚弄 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 不会访问任何不属于这些指针的数据,所以我看到的唯一问题是将 a 别名unsigned char *为 a ,并在预期a 的地方uint8_t *[]传递 a可能会导致各种链接器错误。另外,我不知道 C和 C++是否会在内存中转换成相同的东西(我希望如此)。void *uint8_t *[]boolbool

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

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

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

标签: c++carrayspointersstrict-aliasing

解决方案


保证 T[N][M] 将包含 NxM 类型 T 的连续对象阻碍了一些其他有用的优化;在 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_elementarray[0][i*5]++这又可以优化为array[0][0]++. 我不认为该标准的作者打算邀请编译器进行此类“优化”,但我认为他们认为积极的优化器不会将未能禁止此类事情解释为邀请。


推荐阅读