Передача динамического массива функциям на C

StackOverflow https://stackoverflow.com/questions/341282

  •  19-08-2019
  •  | 
  •  

Вопрос

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

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?Должен ли я внедрять связанный список, чтобы справиться с ними?

Это было полезно?

Решение

Основная проблема здесь в том, что вы пытаетесь использовать 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);

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

Другие советы

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

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

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

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

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

ent *a = NULL;

код будет работать больше так, как вы предполагали, если не освобождать массив, выделенный стеком.Установка a в значение NULL работает, потому что realloc обрабатывает это так, как если бы пользователь вызвал malloc(size) .Имейте в виду, что с этим изменением прототип AddValue должен измениться на

int addValues(ent **a, int mSize)

и что код должен обрабатывать случай сбоя перераспределения.Например

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;

ненужный.

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

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, вам нужно передать ссылку на него в <=>. В данный момент значение a копируется и передается в <=>. Чтобы передать ссылку на использование:

int addValues (int **a, int mSize)

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

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

В <=> получите доступ к таким элементам, как это:

(*a)[element]

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top