在C语言中从函数返回多维数组

5

如何在C语言函数中返回多维数组是最佳方式?

假设我们需要在一个函数中生成一个多维数组,并在main函数中调用它,最好将其包装在一个结构体中还是只返回指向堆上内存的指针?

 int *create_array(int rows, int columns){
     int array[rows][columns] = {0};
     return array;
 }

 int main(){

     int row = 10;
     int columns = 2;
     create_array(row,columns); 
 }

上面的代码仅仅是我想到的基本程序的草稿。

5
在SO上有很多关于这个主题的问题。你看过哪些问题并且无法理解答案?注意不要返回指向局部变量的指针,并避免这样做。 - Jonathan Leffler
1
我希望你的草图不会最终导致实际代码,因为从自动数组返回基地址是没有好处的,其生命周期在包含函数返回时就结束了。 - WhozCraig
是的,我看到了编译器警告,主要是通过结构体或指向指针的指针来处理,我只想知道规范的方法是什么。 - NiallJG
学习堆和栈的区别会对你有所帮助。 - Mawg says reinstate Monica
结构体也可以在堆上分配。 - NiallJG
4个回答

7

这是错误的:

int *create_array(int rows, int columns){
     int array[rows][columns] = {0};
     return array;
}

并且应该产生如下警告:
prog.c:2:6: note: (near initialization for 'array')
prog.c:3:13: warning: return from incompatible pointer type [-Wincompatible-pointer-types]
      return array;
             ^~~~~
prog.c:3:13: warning: function returns address of local variable [-Wreturn-local-addr]

由于您正在返回自动变量的地址,因此当相应函数终止时其生命周期结束。


您可以在main()中声明双指针,将其通过函数传递,为其动态分配内存并返回该指针。或者您可以在main()中创建数组,并将双指针传递给函数。


我想知道在堆上分配多维数组并传递它们的方法

要在堆上分配内存,您可以使用以下两种涉及指针的方法:

#include <stdio.h>
#include <stdlib.h>

// We return the pointer
int **get(int N, int M) /* Allocate the array */
{
    /* Check if allocation succeeded. (check for NULL pointer) */
    int i, **array;
    array = malloc(N*sizeof(int *));
    for(i = 0 ; i < N ; i++)
        array[i] = malloc( M*sizeof(int) );
    return array;
}

// We don't return the pointer
void getNoReturn(int*** array, int N, int M) {
    /* Check if allocation succeeded. (check for NULL pointer) */
    int i;
    *array = malloc(N*sizeof(int *));
    for(i = 0 ; i < N ; i++)
        (*array)[i] = malloc( M*sizeof(int) );
}

void fill(int** p, int N, int M) {
    int i, j;
    for(i = 0 ; i < N ; i++)
        for(j = 0 ; j < M ; j++)
            p[i][j] = j;
}

void print(int** p, int N, int M) {
    int i, j;
    for(i = 0 ; i < N ; i++)
        for(j = 0 ; j < M ; j++)
            printf("array[%d][%d] = %d\n", i, j, p[i][j]);
}

void freeArray(int** p, int N) {
    int i;
    for(i = 0 ; i < N ; i++)
        free(p[i]);
    free(p);
}

int main(void)
{
    int **p;
    //getNoReturn(&p, 2, 5);
    p = get(2, 5);
    fill(p ,2, 5);
    print(p, 2, 5);
    freeArray(p ,2);
    return 0;
}

选择最适合您风格的选项。

是的,这就是为什么我想知道在堆上分配多维数组并传递它们的最佳方法。 - NiallJG
*table[i] 应该改为 (*table)[i] - mch
void getNoReturn(int*** table, int N, int M):请不要做三星程序员,仅返回指针。 - chqrlie
我在这里没有看到2D数组,只有1D数组,实际上是一个1D int 指针数组和N个1D int 数组。 - alk
你的意思是函数名,对吗?修改过了,现在可以了吗?chqrlie我不是这个意思,我只是想提供一个不同的方法...你想让我更新我的答案并提示返回指针吗? - gsamaras

3

如何在C语言中从函数返回多维数组是最好的方式?

我的建议是避免这样做,并且避免在C语言中使用多维数组(它们难以阅读和麻烦)。

我建议将您的矩阵类型作为您的适当抽象数据类型,由一些以灵活数组成员结尾的struct表示:

struct mymatrix_st {
  unsigned nbrows, nbcolumns;
  int values[];
};

这里是创建函数(返回一个正确初始化的指向动态内存的指针):
struct mymatrix_st*
create_matrix(unsigned mnbrows, unsigned mnbcolumns) {
  if (mnbrows > UINT_MAX/4 || mnbcolumns > UINT_MAX/4
      ||(unsigned long)mnbrows * (unsigned long)mnbcolums
        > UINT_MAX) {
   fprintf(stderr, "too big matrix\n");
   exit(EXIT_FAILURE);
 };
 size_t sz = sizeof(struct mymatrix_st)+(mnbrows*mnbcolumns*sizeof(int));
 struct mymatrix_st*m = malloc(sz);
 if (!m) { 
   perror("malloc mymatrix"); exit(EXIT_FAILURE); };
 m->nbrows = mnbrows;
 m->nbcolumns = mnbcolumns;
 for (unsigned long ix=(unsigned long)mnbrows * (unsigned long)mnbcolumns-1;
      ix>=0; ix--)
   m->values[ix] = 0;
 return m;;
} /*end create_matrix*/

有意地,struct mymatrix_st 不包含任何内部指针。您可以并且应该使用 free 来销毁它。

这是访问函数;将其设置为 static inline 函数,并在声明 struct mymatrix_stcreate_matrix 的同一头文件中定义,例如:

static inline int getmatrix(struct mymatrix_st*m, unsigned row, unsigned col) {
  if (!m) {
     fprintf(stderr, "getmatrix with no matrix\n");
     exit(EXIT_FAILURE);
  };
  if (row >= m->nbrows || col >= m->nbcolumns){
     fprintf(stderr, "getmatrix out of bounds\n");
     exit(EXIT_FAILURE);
  };
  return m->values[row*m->nbcolumns + col];
}

我将让你自己定义并实现抽象struct mymatrix_st类型的其他操作。

(你可以调整代码,也许删除越界检查,但我不建议使用不安全的代码)


1
在我看来,这种写法比可接受的多次动态内存分配要好得多。(虽然我会删除“m”空指针检查,并仅在边界处使用assert进行检查,以便在开启NDEBUG时可以优化掉这些检查,因为我喜欢快速而略有危险的代码):D。 - Petr Skocik

0
int** create_array(int rows, int columns){
    int** array = malloc(rows * sizeof(int*));
    int i;
    for (i=0; i<rows; i++)
        array[i] = malloc(columns * sizeof(int));
    return array;
}

应该可以解决问题。如果您使用int array[rows][columns];,则一旦函数返回,它就会死亡,并且您会得到一个UB。您至少应该使用动态内存分配。


我这里看不到二维数组,只有一维数组,实际上是一个一维int指针数组和N个一维int数组。 - alk

0

你不能返回一个数组,但是你可以返回一个普通指针,并记录调用者可能将其视为指向多维数组的指针,该数组的维度与它传递给调用者的维度相同。

(请注意,返回的指针必须指向动态或静态内存,而不是自动内存--不要返回指向局部变量的指针!)

这需要一些略微冗长的转换和可能的宏,但是它是可行的:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
void* 
multi(int R, int C)
{
    return calloc ( 1, sizeof(int[R][C])  ); //or sizeof(int)*R*C
}


int main()
{
    int (*r_)[3][4] = multi(3,4);
    if(!r_) return EXIT_FAILURE;    

#define r (*r_)
    //emulate C++ a reference  -- r now behaves as an `int r[3][4];`

    //Test that addresses advance as they would in a multi-d array

    int local[3][4];
    assert(&local[1][0]-&local[0][0] == 4); //base example
    assert(&r[1][0]-&r[0][0] == 4);         //"returned" multi-d array

    free(r); //or free(&r) or free(r_) -- here it shouldn't matter


#undef r

    return 0;

}

请注意,指针数组并不等同于多维数组。真正的多维数组是一个连续的块,而指针数组(虽然可以使用相同的索引语法)引用的空间局部性参差不齐,因此如果你想获得更好的性能,可能会比返回指向指针的指针更优。

1
请注意,VLA 是 C 的一个较新的特性,并且 sizeofVLA 上是唯一不是编译时常量的情况。我不建议在此处在调用 calloc 时使用它,而只需使用 calloc(R*C,sizeof(int)) - Basile Starynkevitch
为什么不让 multi 返回一个 void* 并简单地写成:int (*r)[][4] = multi(3,4); if(!r) return EXIT_FAILURE; - chqrlie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接