我写了奈维姆矩阵乘法码,以完成我对C99协议的理解。让我有点困惑的是,当我在函数定义的参数列表中声明指向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;
}
}
}发布于 2015-08-11 03:46:44
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中数组的越界访问,从而提交另一个UB。
至于运行它..。在我看来,double (*m)[n_rows]代码“运行良好”是从何而来的?一个快速的实验表明,如果你幸运的话,像这样对编译器撒谎会导致不正确的填充数组。
http://coliru.stacked-crooked.com/a/6032864f2baa2eae
如果你不是那么幸运的话
http://coliru.stacked-crooked.com/a/7ba1002e3150bd1c
发布于 2015-08-11 03:37:40
定义:
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;
}
}
}发布于 2015-08-11 03:53:47
您需要指定除第一个维度之外的所有维度的大小。这是因为多维数组存储在相邻的内存位置中,因此需要使用维度来计算起始地址的实际偏移量,以找到第一维度中的每个元素。
你的二维数组有行和列(在高层意义上)。在记忆中,它可能是这样的:
Row 0 | 1 | 2 ...
+----+----+----|----+----+----|----+----+
| | | | | | | | | ...
+----+----+----|----+----+----|----+----+
Col 0 1 2 | 0 1 2 | 0 1以array[i][j]形式进行的数组访问相当于指针array:*(*(array + i) + j)的加法和取消引用。
array + i用于抵消行号i (并取消引用以给出保持在该行中的数组),j用于使您进一步了解该行中的jth元素。显然,要获得正确的行偏移量,编译器必须知道每一行的大小。
例如,在上面的示例中,编译器需要知道跳过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]会给出两个完全不同的结果,第一个结果是预期的结果,第二个结果是由于类型不匹配而呈现的结果。
https://stackoverflow.com/questions/31932730
复制相似问题