Передача динамического массива функциям на C
Вопрос
Я пытаюсь создать функцию, которая принимает массив в качестве аргумента, добавляет к нему значения (увеличивая его размер при необходимости) и возвращает количество элементов.До сих пор у меня есть:
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 ;, который представляет собой массив динамического размера (легко изменяемый с помощью одного вызова, который вам не нужно писать и отлаживать самостоятельно). р>