Динамический массив в C - realloc - PullRequest
7 голосов
/ 27 сентября 2011

Прежде чем я начну

  1. Я искал "Вопросы с похожими названиями", и хотя я нашел некоторую очень полезную информацию, я просто не могу заставить ее работать.

  2. Это связано с домашней работой. Хотя, не сам проект. Я выполнил это, я просто портирую это из Java в C, чтобы я мог протестировать с моей структурой модульного тестирования профессора.

ОК, динамически размещаемые массивы. Я знаю, как их строить, но не умею их выращивать.

например, у меня есть следующий интерфейс ..

void insertVertex( vertex p1, vertex out[], int *size);

Этот метод берет вершину и сохраняет ее в массиве out. После сохранения вершины я увеличиваю количество длин для будущих вызовов.

p1 - это вершина, которую я собираюсь добавить.

out [] - массив, в котором я должен его хранить (который всегда заполнен)

длина - текущая длина

Вершина определяется как ..

typedef struct Vertex{
int x;
int y;
} Vertex;

Это то, что я использую в Java ..

Vertex tempOut = new Vertex[size +1];
//Code to deep copy each object over
tempOut[size] = p1;
out = tempOut;

Это то, что, как я полагал, я мог бы использовать в с ..

out = realloc(out, (*size + 1) * sizeof(Vertex));
out[(*size)] = p1;

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

Я нашел решение, которое решит эту проблему. Вместо того, чтобы использовать Vertex *, я собирался переключиться на Vertex ** и хранить указатели против вершины. Однако после того, как все переключилось, я обнаружил, что просмотрел тот факт, что модульный тест предоставит мне Vertex out [], в котором все должно храниться.

Я тоже безуспешно попробовал следующее.

Vertex* temp = (Vertex *)malloc((*size + 1) * sizeof(Vertex));
for(int i = 0; i < (*size); i++)
{
temp[i] = out[i];
}
out = temp;

Однако, что бы я ни делал, когда я тестирую после того, как оба из них, возвращаемый массив не изменился.

Любая помощь приветствуется.

Обновление - запрашиваемая информация

out - определяется как массив вершин (Vertex out [])

Он изначально построен с номером вершины в моем многоугольнике. Например.

out = (Vertex *) malloc (vertexInPolygon * sizeof (Vertex))

Где vertexInPolygon - целое число от числа вершин в многоугольнике.

длина была опечатка, которая должна была быть размером.

Размер - целочисленный указатель

int *size = 0;

Каждый раз, когда вершина находится в плоскости отсечения, мы добавляем ее в массив вершин и увеличиваем размер на единицу.

Update

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

#include <stdio.h>
#include <stdlib.h>

typedef struct Vertex {
    int x, y;
} Vertex;

void addPointerToArray(Vertex v1, Vertex out[], int *size);

void addPointerToArray(Vertex v1, Vertex out[], int *size)
{
    int newSize = *size;
    newSize++;

    out = realloc(out, newSize * sizeof(Vertex));
    out[(*size)] = v1;

    //  Update Size
    *size = newSize;
}

int main (int argc, const char * argv[])
{
    //  This would normally be provided by the polygon
    int *size = malloc(sizeof(int)); *size = 3;

    //  Build and add initial vertex
    Vertex *out = (Vertex *)malloc((*size) * sizeof(Vertex));
    Vertex v1; v1.x = 1; v1.y =1;
    Vertex v2; v2.x = 2; v2.y =2;
    Vertex v3; v3.x = 3; v3.y =3;

    out[0] = v1;
    out[1] = v2;
    out[2] = v3;

    //  Add vertex
    //  This should add the vertex to the last position of out
    //  Should also increase the size by 1;
    Vertex vertexToAdd; vertexToAdd.x = 9; vertexToAdd.y = 9;
    addPointerToArray(vertexToAdd, out, size);

    for(int i =0; i < (*size); i++)
    {
        printf("Vertx: (%i, %i) Location: %i\n", out[i].x, out[i].y, i);
    }

}

Ответы [ 4 ]

4 голосов
/ 27 сентября 2011

Одна долгосрочная проблема заключается в том, что вы не возвращаете обновленный указатель массива из функции addPointerToArray():

void addPointerToArray(Vertex v1, Vertex out[], int *size)
{
    int newSize = *size;
    newSize++;

    out = realloc(out, newSize * sizeof(Vertex));
    out[(*size)] = v1;

    //  Update Size
    *size = newSize;
}

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

Vertex *addPointerToArray(Vertex v1, Vertex out[], int *size)
{
    int newSize = *size;
    newSize++;

    out = realloc(out, newSize * sizeof(Vertex));
    out[(*size)] = v1;

    //  Update Size
    *size = newSize;
    return out;
}

и вызов:

out = addPointerToArray(vertexToAdd, out, size);

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

void addPointerToArray(Vertex v1, Vertex **out, int *size)
{
    int newSize = *size;
    newSize++;

    *out = realloc(*out, newSize * sizeof(Vertex));
    (*out)[(*size)] = v1;

    //  Update Size
    *size = newSize;
}

и вызов:

out = addPointerToArray(vertexToAdd, &out, size);

Ни одна из этих переписок не устраняет утечку памяти. Проблема в том, что если вы перезаписываете значение, которое вы передаете в realloc(), возвращаемым значением, но realloc() не удается, вы теряете указатель на (все еще) выделенный массив - утечка памяти. Когда вы используете realloc(), используйте идиому вроде:

Vertex *new_space = realloc(out, newSize * sizeof(Vertex));
if (new_space != 0)
    out = new_space;
else
    ...deal with error...but out has not been destroyed!...

Обратите внимание, что использование realloc() для добавления одного нового элемента за раз приводит к (может привести к) квадратичному поведению. Было бы лучше выделить большой кусок памяти - например, удвоить выделенное пространство:

int newSize = *size * 2;

Если вас беспокоит перераспределение, в конце цикла чтения вы можете использовать realloc(), чтобы уменьшить выделенное пространство до точного размера массива. Тем не менее, есть еще немного бухгалтерского учета, чтобы сделать; вам нужно указать значения: количество вершин, выделенных массиву, и количество фактически используемых вершин.

Наконец, по крайней мере пока, обратите внимание, что вы действительно должны быть безжалостно последовательными и использовать addPointerToArray(), чтобы добавить первые три записи в массив. Я бы, вероятно, использовал что-то похожее на этот (не проверенный) код:

struct VertexList
{
    size_t    num_alloc;
    size_t    num_inuse;
    Vertex   *list;
};

void initVertexList(VertexList *array)
{
    // C99: *array = (VertexList){ 0, 0, 0 };
    // Verbose C99: *array = (VertexList){ .num_inuse = 0, .num_alloc = 0, .list = 0 };
    array->num_inuse = 0;
    array->num_alloc = 0;
    array->list      = 0;
}

void addPointerToArray(Vertex v1, VertexList *array)
{
    if (array->num_inuse >= array->num_alloc)
    {
        assert(array->num_inuse == array->num_alloc);
        size_t new_size = (array->num_alloc + 2) * 2;
        Vertex *new_list = realloc(array->list, new_size * sizeof(Vertex));
        if (new_list == 0)
            ...deal with out of memory condition...
        array->num_alloc = new_size;
        array->list      = new_list;
    }
    array->list[array->num_inuse++] = v1;
}

При этом используется нелогичное свойство realloc(), которое он будет делать malloc(), если переданный указатель будет нулевым. Вместо этого вы можете проверить array->list == 0 и использовать malloc() тогда и realloc() в противном случае.

Вы можете заметить, что эта структура также упрощает вызывающий код; вам больше не нужно иметь дело с отдельным int *size; в основной программе (и ее распределением памяти); размер эффективно объединяется в структуру VertexList как num_inuse. Основная программа может теперь запуститься:

int main(void)
{
    VertexList array;
    initVertexList(&array);
    addPointerToArray((Vertex){ 1, 1 }, &array);  // C99 compound literal
    addPointerToArray((Vertex){ 2, 2 }, &array);
    addPointerToArray((Vertex){ 3, 3 }, &array);
    addPointerToArray((Vertex){ 9, 9 }, &array);

    for (int i = 0; i < array->num_inuse; i++)
        printf("Vertex %d: (%d, %d)\n", i, array->list[i].x, array->list[i].y, i);

    return 0;
}

(Это случайно, что эта последовательность будет вызывать выделение памяти только один раз, потому что новый размер (old_size + 2) * 2 выделяет 4 элемента в массив в первый раз. Легко осуществить перераспределение, добавив новую точку или уточнив формула (old_size + 1) * 2, или ...

Если вы планируете восстанавливаться после сбоя выделения памяти (а не просто выходить из него, если это произойдет), вам следует изменить addPointerToArray(), чтобы он возвращал состояние (успешно, не успешно).

Кроме того, имя функции, вероятно, должно быть addPointToArray() или addVertexToArray() или даже addVertexToList().

1 голос
/ 27 сентября 2011

У меня есть несколько предложений для рассмотрения:
1. Не используйте тот же параметр ввода и вывода при использовании realloc, поскольку он может возвращать NULL в случае сбоя выделения памяти и утечки памяти, указанной ранее. realloc может вернуть новый блок памяти (спасибо @Jonathan Leffler за указание, я пропустил это). Вы можете изменить свой код на что-то в этих строках:

Vertex * new_out = realloc(out,  newSize * sizeof(Vertex));
if( NULL != new_out )
{    
    out = new_out;
    out[(*size)] = v1;
}
else
{
 //Error handling & freeing memory
}

2. Добавить NULL проверяет malloc вызовы и обрабатывать ошибки при сбое памяти.
3. Звонки на номер free отсутствуют.
4. Измените тип возврата addPointerToArray() с void на bool, чтобы указать, успешно ли добавлено. В случае неудачи realloc вы можете вернуть ошибку, скажем, false, иначе вы можете вернуть успех, скажем, true.
Другие наблюдения, связанные с избыточным количеством копий и т. Д., Уже указаны @ MatthewD.
И несколько хороших наблюдений @Jonathan Leffler (: Надеюсь, это поможет!

0 голосов
/ 27 сентября 2011

Попробуйте эти изменения, это должно сработать.

void addPointerToArray(Vertex v1, Vertex (*out)[], int *size)
{
    int newSize = *size;
    newSize++;

    *out = realloc(out, newSize * sizeof(Vertex));
    *out[(*size)] = v1;

    //  Update Size
    *size = newSize;
}

и вызовите функцию наподобие

addPointerToArray(vertexToAdd, &out, size);
  • Существует простой способ исправить эти типыпроблема (вы, возможно, уже знаете это).Когда вы передаете аргумент функции, подумайте, что именно происходит со стеком, а затем объедините тот факт, что все, что вы вносите в переменные, присутствующие в стеке, исчезнет, ​​когда выйдет из функции.Такое мышление должно решить большинство вопросов, связанных с передачей аргументов.

  • Что касается оптимизации, то выбор правильной структуры данных имеет решающее значение для успеха любого проекта.Как указывалось выше, список ссылок для вас лучше, чем массив.

0 голосов
/ 27 сентября 2011

Ваш пример программы отлично работает для меня. Я использую gcc 4.1.1 в Linux.

Однако, если ваша настоящая программа похожа на пример программы, она довольно неэффективна!

Например, ваша программа много копирует память: копирует структуру - инициализирует out, передает вершины в addPointerToArray(), копирует память через realloc().

Передавать структуры через указатель, а не через копию.

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

Если вам просто нужно иметь векторный тип, стандартный метод реализации векторов с динамическим размером состоит в том, чтобы выделить блок памяти (скажем, место для 16 вершин) и удваивать его размер каждый раз, когда у вас заканчивается пространство. Это ограничит количество необходимых перераспределений.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...