与2D C风格的阵列崩溃

时间:2014-05-23 11:52:12

标签: c++ c arrays multidimensional-array

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()签名的情况下使其正常工作?

数组不是动态的。

6 个答案:

答案 0 :(得分:3)

可以使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()功能的签名,但允许您在不崩溃的情况下使用它。

我并不是说我推荐这是最好的方法 - 但它会从字面上回答这个问题。请注意,我从endl中取出Print只是为了让我自己相信这会正确地打印整个数组。

改编自C {#34;&#34;数字食谱中的nrutil.c。 (参见例如源代码here - 这就是本书创建可变长度2D数组的方式(实际上它们更进了一步,允许你使用基数1偏移 - 我发现它很可怕,但是代码和#34;看起来像#FOR; FORTRAN这是科学计算的原始语言。)

答案 1 :(得分: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**无关,并且将其或其中任何内容转换为此类型是不正确的。如果您正在使用固定的数组数组(如果需要,可以将其称为2D数组),可以使用模板制作兼容的打印功能:

#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;
}

答案 2 :(得分:2)

数组和指针是不同的类型。在某些情况下,数组被隐式转换(衰减)为指向其第一个元素的指针,就像在这种情况下将数组传递给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]评估M[0][0]类型为(double *)的地址。 将此值投射到(double **)类型是错误的,因为此地址的值为double,而不是指向double的指针。

现在,在print函数中,M[i][j]被评估为*(*(M + i) + j)。这里,变量Mprint函数的本地)是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);    
}

答案 3 :(得分:1)

  

数组不是动态的

     

是否可以在不更改Print()签名的情况下使其正常工作?

不,不可能达到这两个要求。除非您可以将该2D阵列复制到另一个 动态的

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

有关使用数组的信息,请参阅此tutorial。请注意“多维数组”一章。

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

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

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

注意:正如克里斯评论的那样,这在技术上是不允许的。 Another reference

可以执行您的功能并在技术上正确,但这需要该功能特定于数组的宽度。由于您使用C ++,因此您可以使用模板:

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

 Print<sx>(M[2], 3);

旁注:我不相信这种单线是值得的功能。在我看来,它降低了可读性,并没有提高可重用性。

答案 4 :(得分:1)

正如其他人已经指出&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阵列。 )

答案 5 :(得分:0)

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

相关问题