PETSc 的 DMDA 是否依赖于未定义的行为?

计算科学 宠物
2021-12-12 03:21:06

我正在查看DMDAVecGetArray的文档,很惊讶它可以创建一个普通的普通 C 数组,其索引以某种方式从istartistart + size - 1,而不是从 0 到size - 1,或者一个多维数组(也是一个普通的 C 数组),其索引范围from istartto istart + isize - 1, jstarttojstart + jsize - 1等。我不确定它是如何工作的,所以我查看了代码,发现 DMDAVecGetArray 在幕后使用函数 VecGetArray1d、VecGetArray2d 等来创建这些奇怪的索引数组。VecGetArray1d 的代码如下所示:

PetscErrorCode  VecGetArray1d(Vec x,PetscInt m,PetscInt mstart,PetscScalar *a[])
 {
   PetscInt       N;

   VecGetLocalSize(x,&N);
   if (m != N) SETERRQ2(PETSC_COMM_SELF,PETSC_ERR_ARG_OUTOFRANGE,"Local array size %D does not match 1d array dimensions %D",N,m);
   VecGetArray(x,a);
   *a  -= mstart;
   return(0);
 }

至少从表面上看,这看起来类似于在 C 中执行 1 索引数组的技巧,即

int realarray[10];
int *array = &realarray[-1];

除了a上面 PETSc 代码中的指针直接递减,而不是像array上面那样有第二个指针变量。

我在看我认为我在看的东西吗?我很难相信像 PETSc 这样使用良好的库代码会依赖未定义的行为,所以我不确定 PETSc 代码是否真的非法。

究竟发生了什么?

1个回答

呵呵,有趣的问题。如果您仔细阅读 C 标准,您会发现 (C99, 6.5.6.8) 之类的关于指针运算的措辞。

如果指针操作数和结果都指向同一个数组对象的元素,或者超过数组对象的最后一个元素,则计算不应产生溢出;否则,行为未定义。

据我了解,这种限制的基本原理是分段内存模型,其中指针驻留在不同的寄存器中,并且仅创建指向无效内存位置的指针(不取消引用)可能会导致程序崩溃或损坏。

几年前我们在 petsc-dev 上讨论过这个问题。结论是分段记忆系统或多或少已经死了,“技巧”太有用了,不能放弃。如果您需要在具有分段内存的系统上运行此指针算法将失败,您应该使用 plainVecGetArray()并自己进行索引。您可能会使用 C99 VLA 指针来保留结构化数组索引,但这些数组必须从零开始,因此您不会在局部和全局向量之间获得统一的索引。