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





Попробуйте переделать его так, чтобы в него передавался указатель на указатель на массив, то есть ent **a. Тогда вы сможете обновить вызывающего абонента в новом местоположении массива.
Основная проблема здесь в том, что вы пытаетесь использовать 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);
Таким образом, пользователю не нужно точно помнить, как распределять объекты или какой размер массива в настоящее время. Надеюсь, это поможет вам начать.
Спасибо за это! Очень полезная информация - я собираюсь посмотреть, смогу ли я переработать свой код, как предлагаете вы и Хавьер. Если у меня есть несколько динамических массивов разных типов (массив ents, массив foos и т. д.), Можно ли создать единый набор методов для работы со всеми ними?
Вы можете сделать это с помощью шаблонов в C++, но в C. это не лучший способ справиться с этим. Вы можете сделать это с помощью набора хорошо созданных макросов, но это не делает код самым красивым или удобным в сопровождении.
У вас может быть один набор методов для разных типов, но для этого вам нужно использовать указатели void *. Вам также необходимо знать размер каждого элемента и выполнять много приведений между void * и фактическим типом. Это может быть очень опасно и подвержено ошибкам.
Вы можете попробовать сохранить размер элемента массива как часть структуры динамического массива. Хотя вам все равно понадобится много слепков.
Вы передаете указатель массива по значению. Это означает следующее:
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 (...);
Когда я передаю свой массив таким образом, GDB показывает, что адрес массива ptr в функции - 0x0, тогда как адрес arr в main() показывает длинное шестнадцатеричное число. И я получаю ошибку сегментации, когда пытаюсь добавить элемент в arr в этой функции. 0x0 означает NULL? quora.com/…
это хорошая причина использовать ООП. да, вы можете выполнять ООП на C, и это даже выглядит красиво, если все сделано правильно.
в этом простом случае вам не нужны ни наследование, ни полиморфизм, только концепции инкапсуляции и методов:
Xahtep объясняет, как ваш вызывающий абонент может справиться с тем фактом, что realloc () может переместить массив в новое место. Пока вы это делаете, все будет в порядке.
realloc () может стать дорогим, если вы начнете работать с большими массивами. Вот тогда и пора задуматься об использовании других структур данных - связного списка, двоичного дерева и т. д.
В исходном коде используется обычная практика удвоения размера массива при каждом перераспределении, поэтому я не думаю, что это будет слишком дорого.
В этом случае каждое перераспределение в два раза дороже предыдущего, но при условии постоянной скорости увеличения времени, необходимого для этого, потребовалось вдвое больше. Стоимость указана в неявной функции memcpy () для нового местоположения.
Конечно, но это постоянное амортизированное время. Средний случай добавления к вектору указателей - это запись в ptr, увеличение размера и амортизация memcpy на 1-2 пункта. Чтобы добавить к двусвязному списку, нужно выделить 1 для быстрой кучи, 1 ptr для полезной нагрузки и 4 для перекрестных ссылок. Это больше работы.
Тем более, что memcpy на большинстве архитектур значительно быстрее, чем такое же количество непоследовательных записей указателей. Я бы не стал придавать особого значения утверждению, что экспоненциальные массивы работают медленно без профилирования, если элементы добавляются / удаляются только в конце.
Я согласен - никогда не доверяйте никаким заявлениям об эффективности без профилирования.
Я продолжаю получать нулевой указатель: quora.com/… realloc в read_file () перед вызовом add_student () из main ().
Как указано, вы должны передать указатель на указатель, чтобы обновить значение указателя. Но я бы посоветовал изменить дизайн и избегать этой техники, в большинстве случаев ее можно и нужно избегать. Не зная, чего именно вы пытаетесь достичь, трудно предложить альтернативный дизайн, но я на 99% уверен, что это возможно по-другому. И как грустно Хавьер - думайте об объектах, и вы всегда получите лучший код.
Если вы изменили объявление переменной в основном на
ent *a = NULL;
код будет работать так, как вы предполагали, не освобождая выделенный стеком массив. Установка значения 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;
ненужный.
Вы действительно обязаны использовать C? Это было бы отличным применением C++ "std :: vector", который представляет собой именно массив динамического размера (легко изменяемый с помощью одного вызова, который вам не нужно писать и отлаживать самостоятельно).
Вам нужно объединить это с ответом tgamblin, чтобы получить полное решение.