我写了最简单的矩阵乘法代码来完成我对C99 VLA的理解。让我感到困惑的是当我在函数定义中在参数列表中声明指向VLA的指针时。
例如,在fill_matrix_randomly
中,声明为m
的参数double (*m)[n_cols]
编译得很好。 double (*m)[*]
是编译错误,因为[*]
只能出现在声明中。 double (*m)[]
也是一个错误,因为我无法访问不完整类型的数组。直到现在都没什么奇怪的,但是。 double (*m)[n_rows]
编译得很好甚至可以正常运行? double (*m)[1]
或double (*m)[2]
也有效,我在这里真的很困惑。帮助我减少困惑。
#include <stdio.h>
#include <stdlib.h>
static void fill_matrix_randomly(int, int, double (*)[*]);
static void print_matrix(int, int, double (*)[*]);
static void multiply_matrices(int, int, int, double (*restrict)[*],
double (*restrict)[*], double (*restrict)[*]);
int main(void) {
const int a = 1, b = 3, c = 5;
double m[a][c], m2[a][b], m3[b][c];
fill_matrix_randomly(a, b, m2);
fill_matrix_randomly(b, c, m3);
multiply_matrices(a, b, c, m, m2, m3);
print_matrix(a, b, m2);
print_matrix(b, c, m3);
print_matrix(a, c, m);
}
static void fill_matrix_randomly
(int n_rows, int n_cols, double (*m)[n_cols]) {
for (int i = 0; i < n_rows; ++i) {
for (int j = 0; j < n_cols; ++j) {
m[i][j] = (double)rand() / RAND_MAX + 1;
}
}
}
static void print_matrix(int n_rows, int n_cols, double (*m)[n_cols]) {
for (int i = 0; i < n_rows; ++i) {
printf("[ ");
for (int j = 0; j < n_cols; ++j) {
printf("%.3f", m[i][j]);
if (j != n_cols - 1) {
printf(", ");
} else {
printf(" ]\n");
}
}
}
putchar('\n');
}
static void multiply_matrices
(int n, int m, int p, double (*restrict r)[p],
double (*restrict a)[m], double (*restrict b)[p]) {
for (int i = 0; i < n; ++i) {
for (int j = 0; j < p; ++j) {
double sum = 0;
for (int k = 0; k < m; ++k) {
sum += a[i][k] * b[k][j];
}
r[i][j] = sum;
}
}
}
答案 0 :(得分:3)
double(* m)[n_rows]编译得很好,甚至可以正常运行
如果使用类型double (*)[n_rows]
声明了函数参数,但是传递了double (*)[n_columns]
类型的参数,并且n_rows
与n_columns
不同,则行为未定义。
同样适用于double (*m)[1]
和double (*m)[2]
变体。
参数传递要求参数类型与参数类型兼容。在指向数组的情况下,指针必须指向兼容的数组类型。在您的情况下,以下适用
6.7.5.2数组声明符
6 对于要兼容的两种数组类型,两者都应具有兼容的元素类型,并且如果两个数组说明符都存在,则 整数常量表达式,然后两个大小说明符都应该有 相同的常数值。如果在上下文中使用这两种数组类型 这要求它们兼容,如果是,则是未定义的行为 两个大小说明符评估为不相等的值。
显然,没有人可以合理地期望在编译时捕获这样的违规,因为编译器通常无法在编译时预测和实施运行时关系(VLA大小)。
在提交此违规(其本身足以触发UB)之后,您将通过对fill_matrix_randomly
内的数组执行越界访问来继续提交另一个。
至于运行它...你知道double (*m)[n_rows]
“运行良好的代码”的想法对我来说并不清楚。一个快速的实验表明,如果你幸运的话,那样对编译器说谎会导致数据填充不正确
http://coliru.stacked-crooked.com/a/6032864f2baa2eae
如果你没那么幸运就会崩溃
答案 1 :(得分:1)
定义:
static void print_matrix(int n_rows, int n_cols, double (*m)[n_cols]) {
…
}
表示m
是指向数组的指针,其中数组的每个元素都有n_cols
列。这完全是犹太人。
具有恒定行大小(double (*m)[1]
或double (*m)[2]
)的选项以及具有可变行大小的选项。
您可能还需要注意,代码中的这个次要变体也会编译并运行(因为没有随机数生成器的播种)会产生相同的答案:
#include <stdio.h>
#include <stdlib.h>
static void fill_matrix_randomly(int, int, double[*][*]);
static void print_matrix(int, int, double[*][*]);
static void multiply_matrices(int, int, int, double[*][*],
double[*][*], double[*][*]);
int main(void)
{
const int a = 1, b = 3, c = 5;
double m[a][c], m2[a][b], m3[b][c];
fill_matrix_randomly(a, b, m2);
fill_matrix_randomly(b, c, m3);
multiply_matrices(a, b, c, m, m2, m3);
print_matrix(a, b, m2);
print_matrix(b, c, m3);
print_matrix(a, c, m);
}
static void fill_matrix_randomly(int n_rows, int n_cols, double m[n_rows][n_cols])
{
for (int i = 0; i < n_rows; ++i)
{
for (int j = 0; j < n_cols; ++j)
m[i][j] = (double)rand() / RAND_MAX + 1;
}
}
static void print_matrix(int n_rows, int n_cols, double m[n_rows][n_cols])
{
for (int i = 0; i < n_rows; ++i)
{
printf("[ ");
for (int j = 0; j < n_cols; ++j)
{
printf("%.3f", m[i][j]);
if (j != n_cols - 1)
printf(", ");
else
printf(" ]\n");
}
}
putchar('\n');
}
static void multiply_matrices(int n, int m, int p, double r[n][p],
double a[n][m], double b[m][p])
{
for (int i = 0; i < n; ++i)
{
for (int j = 0; j < p; ++j)
{
double sum = 0;
for (int k = 0; k < m; ++k)
sum += a[i][k] * b[k][j];
r[i][j] = sum;
}
}
}
答案 2 :(得分:1)
您需要指定除第一个维度以外的所有尺寸。这是因为多维数组存储在连续的内存位置,因此需要维度来计算起始地址的实际偏移量,以找到第一维中的每个元素。
您的二维数组包含行和列(从高级别的意义上说)。它可能在内存中看起来像这样:
Row 0 | 1 | 2 ...
+----+----+----|----+----+----|----+----+
| | | | | | | | | ...
+----+----+----|----+----+----|----+----+
Col 0 1 2 | 0 1 2 | 0 1
array[i][j]
形式的数组访问等同于指针array
的添加和取消引用:*(*(array + i) + j)
。
array + i
用于将您偏移到行号i
(并取消引用以提供保存在那里的数组),j
用于向您提供更多j
该行中的{1}}元素。很明显,为了获得正确的行偏移量,编译器必须知道每行的大小。
在上面的示例中,例如,编译器需要知道跳过3个内存位置/数据单元以从一行到另一行前进。
更完整的公式如下(请注意编译器会自动执行此扩展):
*((array + (i * #cols/row * sizeof(array elem))
+ (j * sizeof(array elem)))
很明显,提前需要知道的是sizeof(array elem)
和#cols/row
。
sizeof(array elem)
是已知的,因为您指定了存储在多维数组中的基类型(在您的情况下为double
)。但是#cols/row
必须由程序员指定,以便编译器正确计算偏移量。
在所有工作代码示例中,您将为每行中包含的列赋予具体值。但是,在所有这些中,您将获得意外行为,因为特定大小的数组被视为不同大小的数组。通过指定不正确或不匹配的大小,您将调用未定义的行为,因为参数和提供的参数基本上存在类型不匹配。您使用x
列声明了2D数组,但是将其作为包含y
列的数组传递。
声明第二个维度为3
的数组并将其传递给接受具有第二维2
的数组的函数,如下所示,其中相同的内存以两种不同的方式解释:
Row 0 | 1 | 2 ...
+----+----+----|----+----+----|----+----+
| | | | | | | | | ...
+----+----+----|----+----+----|----+----+
Col 0 1 2 | 0 1 2 | 0 1
Row 0 | 1 | 2 | 3 ...
+----+----|----+----|----+----|----+----+
| | | | | | | | | ...
+----+----|----+----|----+----|----+----+
Col 0 1 | 0 1 | 0 1 | 0 1
您可以看到访问array[1][0]
会给您两个完全不同的结果,第一个是预期结果,第二个是由于类型不匹配而呈现的结果。