将 restrict 限定符与 C99 可变长度数组 (VLA) 结合使用
using restrict qualifier with C99 variable length arrays (VLAs)
我正在探索 C99 中简单循环的不同实现如何根据函数签名自动矢量化。
这是我的代码:
/* #define PRAGMA_SIMD _Pragma("simd") */
#define PRAGMA_SIMD
#ifdef __INTEL_COMPILER
#define ASSUME_ALIGNED(a) __assume_aligned(a,64)
#else
#define ASSUME_ALIGNED(a)
#endif
#ifndef ARRAY_RESTRICT
#define ARRAY_RESTRICT
#endif
void foo1(double * restrict a, const double * restrict b, const double * restrict c)
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < 2048; ++i) {
if (c[i] > 0) {
a[i] = b[i];
} else {
a[i] = 0.0;
}
}
}
void foo2(double * restrict a, const double * restrict b, const double * restrict c)
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < 2048; ++i) {
a[i] = ((c[i] > 0) ? b[i] : 0.0);
}
}
/* Undetermined size version */
void foo3(int n, double * restrict a, const double * restrict b, const double * restrict c)
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < n; ++i) {
if (c[i] > 0) {
a[i] = b[i];
} else {
a[i] = 0.0;
}
}
}
void foo4(int n, double * restrict a, const double * restrict b, const double * restrict c)
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < n; ++i) {
a[i] = ((c[i] > 0) ? b[i] : 0.0);
}
}
/* Static array versions */
void foo5(double ARRAY_RESTRICT a[2048], const double ARRAY_RESTRICT b[2048], const double ARRAY_RESTRICT c[2048])
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < 2048; ++i) {
if (c[i] > 0) {
a[i] = b[i];
} else {
a[i] = 0.0;
}
}
}
void foo6(double ARRAY_RESTRICT a[2048], const double ARRAY_RESTRICT b[2048], const double ARRAY_RESTRICT c[2048])
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < 2048; ++i) {
a[i] = ((c[i] > 0) ? b[i] : 0.0);
}
}
/* VLA versions */
void foo7(int n, double ARRAY_RESTRICT a[n], const double ARRAY_RESTRICT b[n], const double ARRAY_RESTRICT c[n])
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < n; ++i) {
if (c[i] > 0) {
a[i] = b[i];
} else {
a[i] = 0.0;
}
}
}
void foo8(int n, double ARRAY_RESTRICT a[n], const double ARRAY_RESTRICT b[n], const double ARRAY_RESTRICT c[n])
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < n; ++i) {
a[i] = ((c[i] > 0) ? b[i] : 0.0);
}
}
当我用
编译时
$ icc -O3 -std=c99 -opt-report5 -mavx -S foo.c
icc: remark #10397: optimization reports are generated in *.optrpt files in the output location
我看到 VLA 案例没有自动矢量化,但是当我添加标志以断言没有别名时 -fno-alias
,它们是。因此,我得出结论,我应该在源代码中规定这一点,因此我尝试通过使用
进行编译来做到这一点
$ icc -O3 -std=c99 -opt-report5 -mavx -DARRAY_RESTRICT=restrict -S foo.c
icc: remark #10397: optimization reports are generated in *.optrpt files in the output location
编译器错误输出包括
foo.c(98): error: "restrict" is not allowed
void foo7(int n, double ARRAY_RESTRICT a[n], const double ARRAY_RESTRICT b[n],
const double ARRAY_RESTRICT c[n])
^
但是如您所见,我的 VLA 参数不允许限制。
所以我的问题是:有没有办法断言 ISO C 中的 VLA 没有别名?
请注意,我可以使用编译指示在源代码中断言没有别名 - 例如simd
、omp simd
、ivdep
等 - 并获得我想要的自动矢量化,但这些不是 ISO C。
在此上下文中,ISO C 表示 C 的最新版本,在撰写本文时当然是 C11 post。
你的原始代码对我来说很失败,消息如下:
void foo7(int n, double ARRAY_RESTRICT a[n], const double ARRAY_RESTRICT b[n], const double ARRAY_RESTRICT c[n])
^
restrict.c:126:1: error: invalid use of ‘restrict’
restrict.c:126:1: error: invalid use of ‘restrict’
restrict.c:145:1: error: invalid use of ‘restrict’
正在转发部分评论
§6.7.6.3 函数声明符(包括原型) 有示例 5,其中说明以下函数原型声明符是等效的:
void f(double (* restrict a)[5]);
void f(double a[restrict][5]);
void f(double a[restrict 3][5]);
void f(double a[restrict static 3][5]);
这是标准中唯一出现 restrict 与数组类型直接关联的地方。 §6.7.6 通常是关于声明符的,而 §6.7.6.2 是关于数组声明符的,在我看来,限制必须出现在数组维度的第一个组件内。在您的上下文中,它应该是:
void foo7(int n, double a[ARRAY_RESTRICT n],
const double b[ARRAY_RESTRICT n],
const double c[ARRAY_RESTRICT n])
如果没有看到标准中的示例并且您提出问题,我不会相信这种表示法!请注意,这适用于数组和 VLA。
此修改后的代码,基于评论,在相同的编译选项下编译干净:
gcc -g -O3 -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \
-Wold-style-definition -Wold-style-declaration -Werror -c new.restrict.c
编译选项要求事先声明非静态函数,因此声明位于文件顶部。我还在源代码中强制使用 #define ARRAY_RESTRICT restrict
,而不是将其保留为编译选项。
编译器是 GCC 4.9.2 运行 Ubuntu 14.04 衍生版本。
File new.restrict.c
:
/* #define PRAGMA_SIMD _Pragma("simd") */
#define PRAGMA_SIMD
#ifdef __INTEL_COMPILER
#define ASSUME_ALIGNED(a) __assume_aligned(a, 64)
#else
#define ASSUME_ALIGNED(a)
#endif
#define ARRAY_RESTRICT restrict
#ifndef ARRAY_RESTRICT
#define ARRAY_RESTRICT
#endif
void foo1(double *restrict a, const double *restrict b, const double *restrict c);
void foo2(double *restrict a, const double *restrict b, const double *restrict c);
void foo3(int n, double *restrict a, const double *restrict b, const double *restrict c);
void foo4(int n, double *restrict a, const double *restrict b, const double *restrict c);
void foo5(double a[ARRAY_RESTRICT 2048], const double b[ARRAY_RESTRICT 2048], const double c[ARRAY_RESTRICT 2048]);
void foo6(double a[ARRAY_RESTRICT 2048], const double b[ARRAY_RESTRICT 2048], const double c[ARRAY_RESTRICT 2048]);
void foo7(int n, double a[ARRAY_RESTRICT n], const double b[ARRAY_RESTRICT n], const double c[ARRAY_RESTRICT n]);
void foo8(int n, double a[ARRAY_RESTRICT n], const double b[ARRAY_RESTRICT n], const double c[ARRAY_RESTRICT n]);
void foo1(double *restrict a, const double *restrict b, const double *restrict c)
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < 2048; ++i)
{
if (c[i] > 0)
{
a[i] = b[i];
}
else
{
a[i] = 0.0;
}
}
}
void foo2(double *restrict a, const double *restrict b, const double *restrict c)
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < 2048; ++i)
{
a[i] = ((c[i] > 0) ? b[i] : 0.0);
}
}
/* Undetermined size version */
void foo3(int n, double *restrict a, const double *restrict b, const double *restrict c)
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < n; ++i)
{
if (c[i] > 0)
{
a[i] = b[i];
}
else
{
a[i] = 0.0;
}
}
}
void foo4(int n, double *restrict a, const double *restrict b, const double *restrict c)
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < n; ++i)
{
a[i] = ((c[i] > 0) ? b[i] : 0.0);
}
}
/* Static array versions */
void foo5(double a[ARRAY_RESTRICT 2048], const double b[ARRAY_RESTRICT 2048], const double c[ARRAY_RESTRICT 2048])
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < 2048; ++i)
{
if (c[i] > 0)
{
a[i] = b[i];
}
else
{
a[i] = 0.0;
}
}
}
void foo6(double a[ARRAY_RESTRICT 2048], const double b[ARRAY_RESTRICT 2048], const double c[ARRAY_RESTRICT 2048])
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < 2048; ++i)
{
a[i] = ((c[i] > 0) ? b[i] : 0.0);
}
}
/* VLA versions */
void foo7(int n, double a[ARRAY_RESTRICT n], const double b[ARRAY_RESTRICT n], const double c[ARRAY_RESTRICT n])
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < n; ++i)
{
if (c[i] > 0)
{
a[i] = b[i];
}
else
{
a[i] = 0.0;
}
}
}
void foo8(int n, double a[ARRAY_RESTRICT n], const double b[ARRAY_RESTRICT n], const double c[ARRAY_RESTRICT n])
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < n; ++i)
{
a[i] = ((c[i] > 0) ? b[i] : 0.0);
}
}
此代码中的 None 参数具有可变修改类型。 foo6
和 foo7
是完全相同的函数签名,除了 int n
。参见 Why do C and C++ compilers allow array lengths in function signatures when they're never enforced?。
这些都是完全一样的:
void foo8(int n, T *a);
void foo8(int n, T a[16]);
void foo8(int n, T a[n]);
版本void foo8(int n, T a[]);
几乎相同,但有一个极端情况,如果T
是一个不完整的类型,则不允许
foo8
可以用 VLA 或非 VLA 调用。
尽管数组声明符具有可变修改类型,array-of-T 调整为 pointer-to-T在计算参数类型之前。所以 T a[n]
被调整为 T *a
这不是可变修改;但是 void foo9(int n, T a[][n]);
确实会为 a
.
生成可变修改类型 T (*)[n]
将restrict
与数组声明符结合起来的最简单方法是实际使用指针形式,这里:
void foo8(int n, T *restrict a ) {
尝试 void foo8(int n, T restrict a[]);
无效,因为它等同于 void foo8(int n, T restrict *a);
。 restrict
是一个限定符,它在语法上的行为与其他限定符(例如 const
.
相同
正如 Jonathan Leffler 所指出的,还有一种替代语法:
void foo8(int n, T a[restrict]) { // n is optional , as before
在这种情况下,允许以两种不同的方式指定同一事物似乎是多余的,但是 there also exists static
只能与数组声明符(而不是指针声明符)一起使用。如果您想使用这种形式的 static
和 restrict
那么别无选择,只能在方括号内添加 restrict
:
void foo8(int n, T a[restrict static n]) {
需要说明的是,最后一个案例仍然不是可变修改类型; static
是一个承诺 a
, 是一个指针 ,指向至少包含 n
个元素的数组的第一个元素。
此外,调用函数时不需要在编译时检查 static
(当然,如果编译器强制执行的话会更好)。
最后一点:restrict
在原型中 probably has no effect,它只在函数定义中有意义。
我正在探索 C99 中简单循环的不同实现如何根据函数签名自动矢量化。
这是我的代码:
/* #define PRAGMA_SIMD _Pragma("simd") */
#define PRAGMA_SIMD
#ifdef __INTEL_COMPILER
#define ASSUME_ALIGNED(a) __assume_aligned(a,64)
#else
#define ASSUME_ALIGNED(a)
#endif
#ifndef ARRAY_RESTRICT
#define ARRAY_RESTRICT
#endif
void foo1(double * restrict a, const double * restrict b, const double * restrict c)
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < 2048; ++i) {
if (c[i] > 0) {
a[i] = b[i];
} else {
a[i] = 0.0;
}
}
}
void foo2(double * restrict a, const double * restrict b, const double * restrict c)
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < 2048; ++i) {
a[i] = ((c[i] > 0) ? b[i] : 0.0);
}
}
/* Undetermined size version */
void foo3(int n, double * restrict a, const double * restrict b, const double * restrict c)
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < n; ++i) {
if (c[i] > 0) {
a[i] = b[i];
} else {
a[i] = 0.0;
}
}
}
void foo4(int n, double * restrict a, const double * restrict b, const double * restrict c)
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < n; ++i) {
a[i] = ((c[i] > 0) ? b[i] : 0.0);
}
}
/* Static array versions */
void foo5(double ARRAY_RESTRICT a[2048], const double ARRAY_RESTRICT b[2048], const double ARRAY_RESTRICT c[2048])
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < 2048; ++i) {
if (c[i] > 0) {
a[i] = b[i];
} else {
a[i] = 0.0;
}
}
}
void foo6(double ARRAY_RESTRICT a[2048], const double ARRAY_RESTRICT b[2048], const double ARRAY_RESTRICT c[2048])
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < 2048; ++i) {
a[i] = ((c[i] > 0) ? b[i] : 0.0);
}
}
/* VLA versions */
void foo7(int n, double ARRAY_RESTRICT a[n], const double ARRAY_RESTRICT b[n], const double ARRAY_RESTRICT c[n])
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < n; ++i) {
if (c[i] > 0) {
a[i] = b[i];
} else {
a[i] = 0.0;
}
}
}
void foo8(int n, double ARRAY_RESTRICT a[n], const double ARRAY_RESTRICT b[n], const double ARRAY_RESTRICT c[n])
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < n; ++i) {
a[i] = ((c[i] > 0) ? b[i] : 0.0);
}
}
当我用
编译时$ icc -O3 -std=c99 -opt-report5 -mavx -S foo.c
icc: remark #10397: optimization reports are generated in *.optrpt files in the output location
我看到 VLA 案例没有自动矢量化,但是当我添加标志以断言没有别名时 -fno-alias
,它们是。因此,我得出结论,我应该在源代码中规定这一点,因此我尝试通过使用
$ icc -O3 -std=c99 -opt-report5 -mavx -DARRAY_RESTRICT=restrict -S foo.c
icc: remark #10397: optimization reports are generated in *.optrpt files in the output location
编译器错误输出包括
foo.c(98): error: "restrict" is not allowed
void foo7(int n, double ARRAY_RESTRICT a[n], const double ARRAY_RESTRICT b[n],
const double ARRAY_RESTRICT c[n])
^
但是如您所见,我的 VLA 参数不允许限制。
所以我的问题是:有没有办法断言 ISO C 中的 VLA 没有别名?
请注意,我可以使用编译指示在源代码中断言没有别名 - 例如simd
、omp simd
、ivdep
等 - 并获得我想要的自动矢量化,但这些不是 ISO C。
在此上下文中,ISO C 表示 C 的最新版本,在撰写本文时当然是 C11 post。
你的原始代码对我来说很失败,消息如下:
void foo7(int n, double ARRAY_RESTRICT a[n], const double ARRAY_RESTRICT b[n], const double ARRAY_RESTRICT c[n])
^
restrict.c:126:1: error: invalid use of ‘restrict’
restrict.c:126:1: error: invalid use of ‘restrict’
restrict.c:145:1: error: invalid use of ‘restrict’
正在转发部分评论
§6.7.6.3 函数声明符(包括原型) 有示例 5,其中说明以下函数原型声明符是等效的:
void f(double (* restrict a)[5]);
void f(double a[restrict][5]);
void f(double a[restrict 3][5]);
void f(double a[restrict static 3][5]);
这是标准中唯一出现 restrict 与数组类型直接关联的地方。 §6.7.6 通常是关于声明符的,而 §6.7.6.2 是关于数组声明符的,在我看来,限制必须出现在数组维度的第一个组件内。在您的上下文中,它应该是:
void foo7(int n, double a[ARRAY_RESTRICT n],
const double b[ARRAY_RESTRICT n],
const double c[ARRAY_RESTRICT n])
如果没有看到标准中的示例并且您提出问题,我不会相信这种表示法!请注意,这适用于数组和 VLA。
此修改后的代码,基于评论,在相同的编译选项下编译干净:
gcc -g -O3 -std=c11 -Wall -Wextra -Wmissing-prototypes -Wstrict-prototypes \
-Wold-style-definition -Wold-style-declaration -Werror -c new.restrict.c
编译选项要求事先声明非静态函数,因此声明位于文件顶部。我还在源代码中强制使用 #define ARRAY_RESTRICT restrict
,而不是将其保留为编译选项。
编译器是 GCC 4.9.2 运行 Ubuntu 14.04 衍生版本。
File
new.restrict.c
:
/* #define PRAGMA_SIMD _Pragma("simd") */
#define PRAGMA_SIMD
#ifdef __INTEL_COMPILER
#define ASSUME_ALIGNED(a) __assume_aligned(a, 64)
#else
#define ASSUME_ALIGNED(a)
#endif
#define ARRAY_RESTRICT restrict
#ifndef ARRAY_RESTRICT
#define ARRAY_RESTRICT
#endif
void foo1(double *restrict a, const double *restrict b, const double *restrict c);
void foo2(double *restrict a, const double *restrict b, const double *restrict c);
void foo3(int n, double *restrict a, const double *restrict b, const double *restrict c);
void foo4(int n, double *restrict a, const double *restrict b, const double *restrict c);
void foo5(double a[ARRAY_RESTRICT 2048], const double b[ARRAY_RESTRICT 2048], const double c[ARRAY_RESTRICT 2048]);
void foo6(double a[ARRAY_RESTRICT 2048], const double b[ARRAY_RESTRICT 2048], const double c[ARRAY_RESTRICT 2048]);
void foo7(int n, double a[ARRAY_RESTRICT n], const double b[ARRAY_RESTRICT n], const double c[ARRAY_RESTRICT n]);
void foo8(int n, double a[ARRAY_RESTRICT n], const double b[ARRAY_RESTRICT n], const double c[ARRAY_RESTRICT n]);
void foo1(double *restrict a, const double *restrict b, const double *restrict c)
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < 2048; ++i)
{
if (c[i] > 0)
{
a[i] = b[i];
}
else
{
a[i] = 0.0;
}
}
}
void foo2(double *restrict a, const double *restrict b, const double *restrict c)
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < 2048; ++i)
{
a[i] = ((c[i] > 0) ? b[i] : 0.0);
}
}
/* Undetermined size version */
void foo3(int n, double *restrict a, const double *restrict b, const double *restrict c)
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < n; ++i)
{
if (c[i] > 0)
{
a[i] = b[i];
}
else
{
a[i] = 0.0;
}
}
}
void foo4(int n, double *restrict a, const double *restrict b, const double *restrict c)
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < n; ++i)
{
a[i] = ((c[i] > 0) ? b[i] : 0.0);
}
}
/* Static array versions */
void foo5(double a[ARRAY_RESTRICT 2048], const double b[ARRAY_RESTRICT 2048], const double c[ARRAY_RESTRICT 2048])
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < 2048; ++i)
{
if (c[i] > 0)
{
a[i] = b[i];
}
else
{
a[i] = 0.0;
}
}
}
void foo6(double a[ARRAY_RESTRICT 2048], const double b[ARRAY_RESTRICT 2048], const double c[ARRAY_RESTRICT 2048])
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < 2048; ++i)
{
a[i] = ((c[i] > 0) ? b[i] : 0.0);
}
}
/* VLA versions */
void foo7(int n, double a[ARRAY_RESTRICT n], const double b[ARRAY_RESTRICT n], const double c[ARRAY_RESTRICT n])
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < n; ++i)
{
if (c[i] > 0)
{
a[i] = b[i];
}
else
{
a[i] = 0.0;
}
}
}
void foo8(int n, double a[ARRAY_RESTRICT n], const double b[ARRAY_RESTRICT n], const double c[ARRAY_RESTRICT n])
{
ASSUME_ALIGNED(a);
ASSUME_ALIGNED(b);
ASSUME_ALIGNED(c);
PRAGMA_SIMD
for (int i = 0; i < n; ++i)
{
a[i] = ((c[i] > 0) ? b[i] : 0.0);
}
}
None 参数具有可变修改类型。 foo6
和 foo7
是完全相同的函数签名,除了 int n
。参见 Why do C and C++ compilers allow array lengths in function signatures when they're never enforced?。
这些都是完全一样的:
void foo8(int n, T *a);
void foo8(int n, T a[16]);
void foo8(int n, T a[n]);
版本void foo8(int n, T a[]);
几乎相同,但有一个极端情况,如果T
是一个不完整的类型,则不允许
foo8
可以用 VLA 或非 VLA 调用。
尽管数组声明符具有可变修改类型,array-of-T 调整为 pointer-to-T在计算参数类型之前。所以 T a[n]
被调整为 T *a
这不是可变修改;但是 void foo9(int n, T a[][n]);
确实会为 a
.
T (*)[n]
将restrict
与数组声明符结合起来的最简单方法是实际使用指针形式,这里:
void foo8(int n, T *restrict a ) {
尝试 void foo8(int n, T restrict a[]);
无效,因为它等同于 void foo8(int n, T restrict *a);
。 restrict
是一个限定符,它在语法上的行为与其他限定符(例如 const
.
正如 Jonathan Leffler 所指出的,还有一种替代语法:
void foo8(int n, T a[restrict]) { // n is optional , as before
在这种情况下,允许以两种不同的方式指定同一事物似乎是多余的,但是 there also exists static
只能与数组声明符(而不是指针声明符)一起使用。如果您想使用这种形式的 static
和 restrict
那么别无选择,只能在方括号内添加 restrict
:
void foo8(int n, T a[restrict static n]) {
需要说明的是,最后一个案例仍然不是可变修改类型; static
是一个承诺 a
, 是一个指针 ,指向至少包含 n
个元素的数组的第一个元素。
此外,调用函数时不需要在编译时检查 static
(当然,如果编译器强制执行的话会更好)。
最后一点:restrict
在原型中 probably has no effect,它只在函数定义中有意义。