繁体   English   中英

二维C样式数组崩溃

[英]Crash with 2D C-style arrays

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

数组不是动态的。

可以使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(这是科学计算的原始语言)。

这个:

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

数组和指针是不同的类型。 在某些情况下,例如在将数组传递给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 } };

定义Msy数组,即3元素,其中每个元素的类型为double[5] ,即5 double的数组。 因此,在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双精度数组不是指向行的指针数组。 就内存而言,多维数组是平坦的。 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( double * )的指针。 您需要将函数原型更改为typedef double my_array_type [3][5]; void Print (my_array_type *M, int j, int i) typedef double my_array_type [3][5]; void Print (my_array_type *M, int j, int i)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM