使用2D c风格的数组崩溃



2D c风格数组的常见问题。这个程序可以编译,但是在调用Print()函数时崩溃:

#include <iostream>
using namespace std;
static const int sx = 5, sy = 3;
static double M[sy][sx] = { { 0.0, 1.0, 2.0, 3.0, 4.0 },
                            { 0.1, 1.1, 2.1, 3.1, 4.1 },
                            { 0.2, 1.2, 2.2, 3.2, 4.2 } };
void Print(double **M, int j, int i)
{
    cerr << M[j][i] << endl;
}
int main()
{
    cerr << M[2][3] << endl;             // Prints 3.2
    Print((double**)&M[0][0], 2, 3);     // Crash
}

我的代码有什么问题?

有可能在不改变Print()签名的情况下使其工作吗?

This:

static double M[sy][sx] = { { 0.0, 1.0, 2.0, 3.0, 4.0 },
                            { 0.1, 1.1, 2.1, 3.1, 4.1 },
                            { 0.2, 1.2, 2.2, 3.2, 4.2 } };

一点也不像double**,将它或其中的任何内容强制转换为这样的类型是不正确的。如果您使用的是固定数组的数组(如果您愿意,可以称之为二维数组),则可以使用模板制作兼容的打印函数:

#include <iostream>
using namespace std;
static const int sx = 5, sy = 3;
static double M[sy][sx] = { { 0.0, 1.0, 2.0, 3.0, 4.0 },
                            { 0.1, 1.1, 2.1, 3.1, 4.1 },
                            { 0.2, 1.2, 2.2, 3.2, 4.2 } };

template<int M>
void Print( double(*ar)[M] , int j, int i )
{
    std::cout << ar[j][i] << 'n';
}
int main()
{
    Print(M, 2, 3);
    return 0;
}

可以使Print函数工作,但是您需要更改分配数组的方式。具体来说,您需要一个指向每行第一个元素的指针向量,并将其作为参数传递。

你可以这样做:

#include <iostream>
#include <cstdlib>
using namespace std;
static const int sx = 5, sy = 3;
static double M[sy][sx] = { { 0.0, 1.0, 2.0, 3.0, 4.0 },
                            { 0.1, 1.1, 2.1, 3.1, 4.1 },
                            { 0.2, 1.2, 2.2, 3.2, 4.2 } };
double **twoD(int nr, int nc) {
// function that allocates a 2D matrix and returns the result
// as a vector of pointers to the first element of each row
  double **p;
  int ii;
  p = (double**)malloc(nr * sizeof(double*));
  p[0] = (double*)malloc(nr * nc * sizeof(double));
  for(ii=1; ii<nr; ii++) {
    p[ii] = p[0] + ii * nc;
  }
  return p;
}
void free2d(double **p) {
  free(p[0]);
  free(p);
}
void Print(double **M, int j, int i)
{
    cerr << M[j][i] << " ";
}
int main()
{
    double **P;
    P = twoD(sy, sx);
    memcpy(P[0], M, sx * sy * sizeof(double));
    cerr << M[2][3] << endl;             // Prints 3.2
    int ii,jj;
    for(ii=0; ii<sy; ii++) {
      for(jj=0; jj<sx; jj++) {
        Print(P, ii, jj);
      }
      cerr << endl;
    }
    free2d(P);
}

正如你所看到的,它不会改变Print()函数的签名,但允许你使用它而不会崩溃。

我并不是说我推荐这是最好的方法,但它确实回答了这个问题。注意,我从Print中去掉了endl,这样我就可以说服自己,这将正确地打印出整个数组。

这是改编自"C中的数值配方"中的nrutil.c(参见这里的示例源代码-这是该书如何创建可变长度的2D数组(实际上他们更进一步,允许您使用基数1偏移量-我觉得这很可怕,但使代码"看起来像"FORTRAN,这是科学计算的原始语言)。

数组和指针是不同的类型。在某些情况下,数组被隐式转换(衰减)为指向其第一个元素的指针,例如在本例中,当数组传递给print function时。

下面的语句

static double M[sy][sx] = { { 0.0, 1.0, 2.0, 3.0, 4.0 },
                            { 0.1, 1.1, 2.1, 3.1, 4.1 },
                            { 0.2, 1.2, 2.2, 3.2, 4.2 } };

M定义为sy的数组,即3元素,其中每个元素的类型为double[5],即5双精度类型的数组。因此,在print函数调用

Print((double**)&M[0][0], 2, 3);

&M[0][0]求值为类型为(double *)M[0][0]的地址。将此值强制转换为(double **)类型是错误的,因为此地址的值是double,而不是指向double的指针。

现在,在print函数中,M[i][j]被计算为*(*(M + i) + j)。这里,变量M (print函数的局部变量)是M[0][0]的地址。因此,*(M + i)求值为M[0][i],即double。接下来,将j添加到该值,并假设该值是一个有效的内存地址,则获取该地址的值。这是完全错误的,并且会调用未定义的行为。

需要修改print函数的签名。

#include <iostream>
using namespace std;
static const int sx = 5, sy = 3;
static double M[sy][sx] = { { 0.0, 1.0, 2.0, 3.0, 4.0 },
                            { 0.1, 1.1, 2.1, 3.1, 4.1 },
                            { 0.2, 1.2, 2.2, 3.2, 4.2 } };
void Print(double *M, int j, int i)
{
    cerr << M[j*sx + i] << endl;
}
int main()
{
    cerr << M[2][3] << endl;             // Prints 3.2
    Print(&M[0][0], 2, 3);    
}

数组不是动态的

有可能使它工作而不改变Print()的签名吗?

不,不可能同时满足两个要求。除非你可以将2D数组复制到另一个是动态的

2D双精度数组不是指向行指针的数组。就内存而言,多维数组是扁平的。M[0][0]的类型是double,因此&M[0][0]的类型是*double

请参阅关于使用数组的教程。请注意"多维数组"一章

你可能想要做的事情可以这样做:

void Print(double *M, int j, int i, int width) {
    cerr << M[i + width * j] << endl;
}
Print(&M[0][0], 2, 3, sx);

注意:正如chris所评论的,这在技术上是不允许的。另一个参考。

执行函数并在技术上是正确的是可能的,但这要求函数特定于数组的宽度。因为您使用c++,所以这不是问题,因为您可以使用模板:

template<size_t X>
void Print(double M[X], int i) {
    cerr << M[i] << endl;
}
 Print<sx>(M[2], 3);

旁注:我不认为这一行代码值得作为一个函数。在我看来,它降低了可读性,并没有提高可重用性。

正如其他人已经指出的那样,&M[0][0]double *,因为您只是检索元素M[0][0]的地址。如果你把它当作double **,然后试图在记忆中大步前进,假装它真的是double **,你基本上是在找麻烦。我猜你想做的事情可以通过显式地进行两层分配来安全实现,例如:

double ** M = new double*[sy];
for (int iy=0; iy<sy; iy++) {
    M[iy] = new double[sx];
}  

则(初始化后)Print(M, 2, 3);将正常工作。恐怕你要放弃数组式的初始化如果你用这种方式来做,并寻找其他的方法。(注意:我上面的建议对于大型数组来说远远不够有效,因为2D数组的各种1D块不能保证连续分配。一般来说,如果大小和性能是问题,您应该考虑将2D数组表示为1D数组。

问题是&M[0][0]不是double **,它只是指向双精度(double *)的指针。您需要将函数原型更改为typedef double my_array_type [3][5]; void Print (my_array_type *M, int j, int i)

相关内容

  • 没有找到相关文章