C中如何将动态数组传递给函数

6
我将尝试创建一个函数,它接受一个数组作为参数,向其添加值(如果需要增加其大小),并返回项目的数量。 到目前为止,我的代码如下:
int main(int argc, char** argv) {
    int mSize = 10;
    ent a[mSize];
    int n;
    n = addValues(a,mSize);

    for(i=0;i<n;i++) {
       //Print values from a
    }
}

int addValues(ent *a, int mSize) {
    int size = mSize;

    i = 0;

    while(....) { //Loop to add items to array
        if(i>=size-1) { 
            size = size*2;
            a = realloc(a, (size)*sizeof(ent));
        }
        //Add to array
        i++;
    }
    return i;
}

如果mSize足够大以容纳数组的所有潜在元素,那么这将起作用,但如果需要调整大小,则会出现“分段错误”。 我也尝试过:
int main(int argc, char** argv) {
    ...
    ent *a;
    ...
}

int addValues(ent *a, int mSize) {
    ...
    a = calloc(1, sizeof(ent);
    //usual loop
    ...
}

徒劳无功。

我猜想这是因为当我调用realloc时,'a'的副本被指向了其他地方 - 如何修改它以使'a'始终指向同一位置?

我的做法正确吗?在C中处理动态结构的更好方法有哪些?我应该实现一个链表来处理这些问题吗?

8个回答

11
这里的主要问题是你试图使用realloc来处理一个栈分配的数组。你有如下代码:
ent a[mSize];

这是在堆栈上自动分配的。如果您以后想使用realloc(),则可以使用malloc()在堆上创建数组,方法如下:

ent *a = (ent*)malloc(mSize * sizeof(ent));

为了让malloc库(因此也包括realloc()等)知道您的数组。从这个样子来看,您可能会将C99可变长度数组与真正的动态数组混淆,因此在尝试修复此问题之前,请确保您了解其中的区别。
但是,如果您正在使用C编写动态数组,则应尝试使用面向对象的设计来封装有关数组的信息并将其隐藏在用户界面之外。您要将有关数组的信息(例如指针和大小)合并到结构中,并将操作(例如分配、添加元素、删除元素、释放等)合并到特殊函数中,这些函数与您的结构一起工作。因此,您可能会有:
typedef struct dynarray {
   elt *data;
   int size;
} dynarray;

您可能需要定义一些函数来处理动态数组:

// malloc a dynarray and its data and returns a pointer to the dynarray    
dynarray *dynarray_create();     

// add an element to dynarray and adjust its size if necessary
void dynarray_add_elt(dynarray *arr, elt value);

// return a particular element in the dynarray
elt dynarray_get_elt(dynarray *arr, int index);

// free the dynarray and its data.
void dynarray_free(dynarray *arr);

这样用户就不必记得如何分配事物或数组当前的大小。希望这可以让你开始。

谢谢!非常有用的信息 - 我会看看是否可以按照你和Javier的建议重新编写我的代码。如果我有多个不同类型的动态数组(一个“ents”数组,一个“foos”数组等),是否可能创建一组方法来处理它们所有? - Tom
你可以在C++中使用模板来实现这个,但是在C语言中没有很好的方法来处理它。你可以通过一组精心制作的宏来完成,但这并不会产生最美观或最易维护的代码。 - Todd Gamblin
你可以为不同类型拥有一组方法,但为了实现这一点,你需要使用void指针。你还需要知道每个元素的大小,并在void和实际类型之间执行大量转换。这可能非常危险且容易出错。 - Adam Rosenfield
你可以尝试将数组元素的大小存储为dynarray结构的一部分。但是你仍然需要进行很多强制类型转换。 - user3458

6
尝试重新构建它,使得传入指向数组的指针的指针,即ent **a。这样,您将能够更新调用者关于数组新位置的信息。

你需要将这个与tgamblin的答案结合起来,才能得到完整的解决方案。 - Mark Ransom

1

这是使用面向对象编程的一个很好的理由。是的,你可以在C语言中使用面向对象编程,如果正确使用,它看起来甚至很不错。

在这个简单的例子中,你不需要继承或多态性,只需要封装和方法的概念:

  • 定义一个具有长度和数据指针的结构体。也许还有元素大小。
  • 编写操作该结构体指针的getter/setter函数。
  • 'grow'函数修改结构体内的数据指针,但任何结构体指针仍然有效。

1

你正在通过值传递数组指针。这意味着:

int main(int argc, char** argv) {
    ...
    ent *a; // This...
    ...
}

int addValues(ent *a, int mSize) {
    ...
    a = calloc(1, sizeof(ent); // ...is not the same as this
    //usual loop
    ...
}

因此,在addValues函数中更改a的值不会更改主函数中a的值。 若要更改主函数中a的值,您需要将其引用传递给addValues。 目前,a的值被复制并传递到addValues。 要传递对a的引用,请使用:

int addValues (int **a, int mSize)

并像这样调用:

int main(int argc, char** argv) {
    ...
    ent *a; // This...
    ...
    addValues (&a, mSize);
}

addValues函数中,可以通过以下方式访问数组a的元素:
(*a)[element]

并重新分配数组,如下所示:

(*a) = calloc (...);

当我像那样传递我的数组时,GDB 显示函数中数组 ptr 的地址为 0x0,而在 main()arr 的地址显示为一个长十六进制。当我尝试在该函数中添加一个元素到 arr 时,出现了分段错误。0x0 表示它是 NULL 吗? https://www.quora.com/Why-am-I-getting-a-null-pointer-to-an-array-on-the-heap-when-I-pass-it-to-a-function-in-C - mLstudent33

1

如果你将main函数中的变量声明改为

ent *a = NULL;

通过不释放堆栈分配的数组,代码将更像您所设想的那样工作。将a设置为NULL是有效的,因为realloc将其视为用户调用malloc(size)。请记住,随着这个改变,addValue的原型需要改变为

int addValues(ent **a, int mSize)

代码需要处理realloc失败的情况。例如

while(....) { //Loop to add items to array
    tmp = realloc(*a, size*sizeof(ent));
    if (tmp) {
        *a = tmp;
    } else {
        // allocation failed. either free *a or keep *a and
        // return an error
    }
    //Add to array
    i++;
}

我预计realloc的大部分实现在当前缓冲区需要调整大小时会内部分配两倍于原有内存的空间,使原始代码得以...
size = size * 2;

不必要的。


0
Xahtep解释了如何处理realloc()可能将数组移动到新位置的问题。只要你这样做,就应该没问题。
如果你开始使用大型数组,realloc()可能会变得很昂贵。这时就该考虑使用其他数据结构——链表、二叉树等。

原始代码使用了每次重新分配时将数组大小加倍的常见做法,因此我认为这不会太昂贵。 - Mark Ransom
在这种情况下,每次realloc的成本是上一次的两倍 - 但假设增长速度恒定,需要的时间加倍。成本在于隐式的memcpy()到新位置。 - slim
当然可以,但这是摊销常数时间。向指针向量添加的平均情况是一个指针写入,增加大小,并摊销1-2个指针的memcpy。要添加到双向链表中,则需要从快速堆中分配1个alloc,1个用于有效负载的指针以及4个用于交叉链接的指针。那是更多的工作。 - Steve Jessop
特别是在大多数架构上,memcpy比相同数量的非连续指针写入要快得多。只要元素仅在末尾添加/删除,我不会过分关注指数数组速度慢的说法,除非进行了分析。 - Steve Jessop
我同意 - 在进行性能优化之前,不要轻信任何效率声明。 - slim
我一直收到一个空指针:https://www.quora.com/Why-am-I-getting-a-null-pointer-to-an-array-on-the-heap-when-I-pass-it-to-a-function-in-C realloc在从main()调用add_student()之前的read_file()中。 - mLstudent33

0
如所述,您应传递指向指针的指针以更新指针值。
但我建议重新设计并避免使用此技术,在大多数情况下可以且应该避免使用。不知道您试图实现什么,很难建议替代设计,但我有99%的把握可以用其他方式完成。正如Javier所说-思考面向对象编程,您将始终获得更好的代码。

0

你真的必须使用C语言吗?这个问题非常适合使用C++的“std::vector”,它是一个动态大小的数组(可以轻松调整大小,而无需编写和调试代码)。


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