Передача динамического массива в функции в C - PullRequest
5 голосов
/ 04 декабря 2008

Я пытаюсь создать функцию, которая принимает массив в качестве аргумента, добавляет к нему значения (при необходимости увеличивая его размер) и возвращает количество элементов. Пока что у меня есть:

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 ]

10 голосов
/ 04 декабря 2008

Основная проблема здесь в том, что вы пытаетесь использовать realloc с массивом, выделенным стеком. У вас есть:

ent a[mSize];

Это автоматическое распределение в стеке. Если вы захотите использовать realloc () для этого позже, вы создадите массив в куче с помощью malloc (), например:

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

Так что библиотека malloc (и, следовательно, realloc () и т. Д.) Знает о вашем массиве. Исходя из этого, вы можете путать массивы переменной длины C99 с истинными динамическими массивами , поэтому убедитесь, что вы понимаете разницу, прежде чем пытаться это исправить.

Действительно, если вы пишете динамические массивы на C, вы должны попытаться использовать дизайн OOP-ish для инкапсуляции информации о ваших массивах и скрытия ее от пользователя. Вы хотите объединить информацию (например, указатель и размер) о вашем массиве в структуру и операции (например, распределение, добавление элементов, удаление элементов, освобождение и т. Д.) В специальные функции, которые работают с вашей структурой. Таким образом, вы можете иметь:

typedef struct dynarray {
   elt *data;
   int size;
} dynarray;

И вы можете определить некоторые функции для работы с dynarrays:

// 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);

Таким образом, пользователю не нужно точно помнить, как распределять объекты или какого размера массив в настоящее время. Надеюсь, что вы начали.

6 голосов
/ 04 декабря 2008

Попробуйте переработать его так, чтобы указатель на указатель на массив был передан, то есть ent **a. После этого вы сможете обновить вызывающую программу на новом месте массива.

1 голос
/ 05 декабря 2008

Если вы изменили объявление переменной в main на

ent *a = NULL;

код будет работать больше, чем вы предполагали, не освобождая массив, выделенный стеком. Установка в 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;

ненужные.

1 голос
/ 04 декабря 2008

это хорошая причина использовать ООП. да, вы можете сделать ООП на C, и даже если все сделано правильно, это даже выглядит красиво.

в этом простом случае вам не нужны ни наследование, ни полиморфизм, а только концепции инкапсуляции и методов:

  • определяет структуру с длиной и указателем данных. возможно размер элемента.
  • написать функции получения / установки, которые работают с указателями на эту структуру.
  • функция 'grow' изменяет указатель данных в структуре, но любой указатель структуры остается действительным.
1 голос
/ 04 декабря 2008

Вы передаете указатель массива по значению. Что это значит:

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

, поэтому изменение значения a в функции addValues не приводит к изменению значения a в main. Чтобы изменить значение a в main, вам нужно передать ссылку на него addValues. В данный момент значение a копируется и передается addValues. Чтобы передать ссылку на использование:

int addValues (int **a, int mSize)

и назовите это как:

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

В addValues, получить доступ к таким элементам, как это:

(*a)[element]

и перераспределить массив следующим образом:

(*a) = calloc (...);
0 голосов
/ 06 декабря 2008

Вы действительно должны использовать C? Это было бы отличным приложением "std :: vector" C ++, который представляет собой массив динамического размера (легко изменяемый с помощью одного вызова, который вам не нужно писать и отлаживать самостоятельно).

0 голосов
/ 04 декабря 2008

Как указано, вы должны передать указатель на указатель, чтобы обновить значение указателя.
Но я бы предложил редизайн и избежать этой техники, в большинстве случаев ее можно и нужно избегать. Не зная, чего именно вы пытаетесь достичь, сложно предложить альтернативный дизайн, но я на 99% уверен, что это выполнимо иначе. И как Хавьер грустно - думайте об объектно-ориентированном и вы всегда получите лучший код.

0 голосов
/ 04 декабря 2008

Xahtep объясняет, как ваш вызывающий может справиться с тем фактом, что realloc () может переместить массив в новое место. Пока вы делаете это, у вас все будет хорошо.

realloc () может дорого обойтись, если вы начнете работать с большими массивами. Вот когда пришло время задуматься об использовании других структур данных - связанного списка, двоичного дерева и т. Д.

...