C99 对指向指针的“限制”的语义是什么?
-
11-09-2019 - |
题
我正在做大量的矩阵算术并且想利用 C99 的优势 restrict
指针限定符。
我想将矩阵设置为指向指针的指针,以便轻松添加下标,如下所示:
int **A = malloc (ncols * sizeof(int *));
A[0] = malloc (nrows * ncols * sizof(int));
for (int i=1; i < ncols; i++) {
A[i] = A[0] + i*nrows;
}
现在,对于矩阵乘法函数
void mmultiply ( int nrows, int ncols, int **Out, int **A, int **B);
我必须将参数的两个指针限定为受限的吗?这是有效的语法,但我很难确定是否 int *restrict *restrict
行为与任何不同 int **restrict
.
然后,在适当限制指针的情况下,通过以下方式访问元素 A[0][col*nrows + row]
不明确的?(即,编译器会假设我 仅有的 通过访问矩阵 A[col][row]
对于值 row
这样 row < nrow
)?或者我必须保持一致?
解决方案
对于第一个问题,“是”,就意味着不同的东西,如果你同时使用restrict
预选赛,具体而言,指针也不会被混淆。至于是否有什么差别:理论上是,在实践中,这取决于优化
有关的第二个问题,“是”,它会假设任何通过一个行指针访问仅通过行指针访问。
您可以在那里太丢const
。
最后,如果这是gcc在-O2,-O3,或-OS,编译器已经做基于类型的别名分析。我相信其他的编译器这样做也。这意味着,限制指针VS的整数已经了解了,只留下那些可能存储到对方的阵列。
在总和,优化器将假设指针没有被存储到作为整数,并且它知道它是没有做任何指针在循环过程中写入。
所以,你可能只用一个限制得到相同的代码。
其他提示
外(第二)限制告诉编译器没有指针的阵列(A,B,和出)别名。内(第一)限制告诉没有整数的阵列的别名(由指针的数组的元素指向的)编译器。
如果您访问两个A [0] [COL * NROWS +排]和A [山口] [行]那么你就违反了内部制约,这样的事情可能打破。
int **restrict
仅断言 Out、A 和 B 寻址的内存不重叠(除非 A 和 B 可以重叠,假设您的函数不修改它们中的任何一个)。这意味着指针数组。它不会断言 Out、A 和 B 所指向的内存内容的任何内容。n1124 中的脚注 117 说:
如果标识符P具有类型(INT **限制),则指针表达式P和P+1基于P指定的限制指针对象,但是指针表达式 *P和P [1]不是。
类推 const
, ,我怀疑符合资格 restrict
times 将断言您想要的内容,即数组中没有任何值指向重叠内存。但阅读该标准后,我无法向自己证明它确实如此。我认为“让 D 是一个普通标识符的声明,它提供了一种将对象 P 指定为类型 T 的限制限定指针的方法”确实意味着 int *restrict *restrict A
, ,则 A[0] 和 A[1] 是指定为 int 的限制限定指针的对象。但这是相当沉重的法律术语。
请注意,我不知道您的编译器是否真的会利用这些知识做任何事情。显然可以,只是执行与否的问题。
所以我真的不知道你从传统的 C 二维数组中获得了什么,你只需分配 rows * cols * sizeof(int)
, ,并索引 A[cols*row + col]
. 。那么你显然只需要一次 limit 的使用,并且任何使用它做任何事情的编译器 restrict
将能够跨对 Out 的写入重新排序从 A 和 B 的读取。没有 restrict
, ,当然,它不能,所以通过做你正在做的事情,你将自己置于编译器的怜悯之下。如果它不能处理双重限制,只能处理单一限制的情况,那么您的双重间接会导致您的优化付出代价。
乍一看,无论如何,乘法可能比附加的指针间接更快。您显然关心性能,否则您根本不会使用限制,因此在进行此更改之前,我会相当仔细地测试性能(在您关心的所有编译器上),以便稍微更好的语法,而不必记住有多少每次访问数组时,数组中都会有列。
通过 A[0][col*nrows + row] 访问元素是否未定义?
是的,如果元素被其中一次访问修改,因为这使得 A[0] 成为也通过 A[col] 访问的内存的别名。如果只有 A 和 B 是限制限定指针,那就没问题,但如果 A[0] 和 A[col] 是限制限定指针,那就不行了。
我假设您没有修改此函数中的 A,所以实际上该别名没问题。但是,如果您对 Out 执行相同的操作,则行为将是不确定的。