перераспределение массива внутри структуры - PullRequest
0 голосов
/ 27 августа 2018

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

Соответствующая часть моего кода:

struct data_t {
  int data_size;
  uint16_t *data;
};

void extend_data(data_t container, uint16_t value) {
    // adds an additional uint16_t to the array of DATA, updates its internal
    // variables, and initialises the new uint to VALUE.

    int len_data = sizeof(*(container->data)) / sizeof(uint16_t);
    printf("LENGTH OF DATA: %d\n", len_data);

    container->data = realloc(container->data, sizeof(*(container->data))+sizeof(uint16_t));
    container->data_size++;
    container->data[container->data_size-1] = value;

    len_data = sizeof(*(container->data)) / sizeof(uint16_t);
    printf("LENGTH OF DATA: %d\n", len_data);
    printf("data_size: %d\n", container->data_size);

    return;
}

Кто-нибудь может увидеть, в чем проблема с этим?

Ответы [ 4 ]

0 голосов
/ 28 августа 2018

Вы не правильно рассчитали новый размер. Учтите это:

typedef struct {
    size_t  size;
    int    *data;
} int_array;
#define  INT_ARRAY_INIT  { 0, NULL}

void int_array_resize(int_array *const  array,
                      const size_t      newsize)
{
    if (!array) {
        fprintf(stderr, "int_array_resize(): NULL int_array.\n");
        exit(EXIT_FAILURE);
    }
    if (!newsize) {
        free(array->data);
        array->data = 0;
        array->size = 0;
    } else
    if (newsize != array->size) {
        void *temp;

        temp = realloc(array->data, newsize * sizeof array->data[0]);
        if (!temp) {
            fprintf(stderr, "int_array_resize(): Out of memory.\n");
            exit(EXIT_FAILURE);
        }
        array->data = temp;
        array->size = newsize;
    }
}

/* int_array  my_array = INT_ARRAY_INIT;
      is equivalent to
   int_array  my_array;
   int_array_init(&my_array);
*/
void int_array_init(int_array *const array)
{
    if (array) {
        array->size = 0;
        array->data = NULL;
    }
}

void int_array_free(int_array *const array)
{
    if (array) {
        free(array->data);
        array->size = 0;
        array->data = NULL;
    }
}

Ключевой момент - newsize * sizeof array->data[0]. Это число символов, необходимое для newsize элементов любого типа, array->data[0]. И malloc(), и realloc() принимают размер в символах.

Если вы инициализируете новые структуры этого типа, используя int_array my_array = INT_ARRAY_INIT;, вы можете просто вызвать int_array_resize(), чтобы изменить его размер. (realloc(NULL, size) эквивалентно malloc(size); free(NULL) безопасно и ничего не делает.)

int_array_init() и int_array_free() - это просто вспомогательные функции для инициализации и освобождения таких массивов.


Лично, когда у меня есть динамически изменяемые размеры массивов, я сохраняю как выделенный размер (size), так и используемый размер (used):

typedef struct {
    size_t  size;   /* Number of elements allocated for */
    size_t  used;   /* Number of elements used */
    int    *data;
} int_array;
#define  INT_ARRAY_INIT { 0, 0, NULL }

Функция, обеспечивающая наличие как минимум need элементов, которые можно добавить, особенно полезна. Чтобы избежать ненужных перераспределений, функция реализует политику, которая вычисляет новый размер для выделения, как баланс между количеством памяти, «потраченной впустую» (выделенной, но не использованной), и количеством потенциально медленных realloc() вызовов:

void int_array_need(int_array *const  array,
                    const size_t      need)
{
    size_t  size;
    void   *data;

    if (!array) {
        fprintf(stderr, "int_array_need(): NULL int_array.\n");
        exit(EXIT_FAILURE);
    }

    /* Large enough already? */
    if (array->size >= array->used + need)
        return;

    /* Start with the minimum size. */
    size = array->used + need;

    /* Apply growth/reallocation policy. This is mine. */
    if (size < 256)
        size = (size | 15) + 1;
    else
    if (size < 2097152)
        size = (3 * size) / 2;
    else
        size = (size | 1048575) + 1048577 - 8;

    /* TODO: Verify (size * sizeof array->data[0]) does not overflow. */

    data = realloc(array->data, size * sizeof array->data[0]);
    if (!data) {
        /* Fallback: Try minimum allocation. */
        size = array->used + need;
        data = realloc(array->data, size * sizeof array->data[0]);
    }
    if (!data) {
        fprintf(stderr, "int_array_need(): Out of memory.\n");
        exit(EXIT_FAILURE);
    }

    array->data = data;
    array->size = size;
}

Существует много мнений о том, какую политику перераспределения следует использовать, но это действительно зависит от варианта использования.

В балансе три вещи: количество вызовов realloc(), так как они могут быть «медленными»; фрагментация памяти, если различные массивы растут, требуя много вызовов realloc(); и объем памяти, выделенной, но не использованной.

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

Для больших выделений 50% добавляется к размеру. Это уменьшает количество вызовов realloc(), сохраняя выделенную, но неиспользуемую / ненужную память ниже 50%.

Для действительно больших выделений, когда у вас есть 2 21 элементов или больше, размер округляется до следующего кратного 2 20 , минус несколько элементов. Это ограничивает количество выделенных, но неиспользуемых элементов примерно до 2 21 или двух миллионов элементов.

(Почему не так много элементов? Потому что это не наносит вреда ни на каких системах, а на некоторых системах это может сильно помочь. Некоторые системы, включая x86-64 (64-битный Intel / AMD) в определенных операционных системах и конфигурациях Поддержка больших («огромных») страниц, которые могут быть более эффективными в некоторых отношениях, чем обычные страницы. Если они используются для удовлетворения распределения, я хочу избежать случая, когда очень большая страница выделяется просто для обслуживания нескольких байтов внутренняя библиотека C необходима для метаданных выделения.)

0 голосов
/ 27 августа 2018

Редактировать

Как указывает Р. Саху, container не является указателем в этой функции - когда вы сказали, что код "не работает", я предположил, что вы имели в виду, что вы не увеличивали свой массив, но то, что вы ' написано здесь, даже не будет компилировать .

Вы уверены, что правильно скопировали этот код? Если так, значит «не работает» означает, что вы получаете ошибку во время компиляции, ошибку во время выполнения или просто неожиданный вывод?

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

void extend_data(data_t *container, uint16_t value) {

и убедитесь, что вы передаете указатель 1016 * на ваш тип data_t, иначе обновление не будет отражено в вызывающем коде.

Оригинал

В строке

container->data = realloc(container->data, sizeof(*(container->data))+sizeof(uint16_t));

sizeof(*(container->data)) оценивается как sizeof (uint16_t). container->data - это указатель , а не массив, uint16_t; sizeof даст вам размер объекта указателя, а не количество выделенных вами элементов. Что вы хотите сделать, это что-то вроде следующего:

/**
 * Don't assign the result of a realloc call back to the original
 * pointer - if the call fails, realloc will return NULL and you'll
 * lose the reference to your original buffer.  Assign the result to
 * a temporary, then after making sure the temporary is not NULL,
 * assign that back to your original pointer.
 */
uint16_t *tmp = realloc(container-data, sizeof *container->data * (container->data_size + 1) );
if ( tmp ) 
{
  /**
   * Only add to container->data and update the value of container->data_size
   * if the realloc call succeeded.
   */
  container->data = tmp;
  container->data[container->data_size++] = value;
}
0 голосов
/ 28 августа 2018

Похоже, вы не используете sizeof правильно. В вашей структуре вы определили uint16_t указатель , а не массив. Размер типа данных uint16_t* - это размер указателя в вашей системе. Вам необходимо сохранить размер выделенной памяти вместе с указателем, если вы хотите иметь возможность точно изменить ее размер. Похоже, у вас уже есть поле для этого с data_size. Ваш пример может быть исправлен как

// I was unsure of the typedef-ing happening with data_t so I made it more explicit in this example
typedef struct {
    int data_size;
    uint16_t* data;
} data_t;

void extend_data(data_t* container, uint16_t value) {
    // adds an additional uint16_t to the array of DATA, updates its internal
    // variables, and initialises the new uint to VALUE.

    // CURRENT LENGTH OF DATA
    int len_data = container->data_size * sizeof(uint16_t);
    printf("LENGTH OF DATA: %d\n", len_data);

    uint16_t* tmp = realloc(container->data, (container->data_size + 1) * sizeof(uint16_t));
    if (tmp) {
        // realloc could fail and return false.
        // If this is not handled it could overwrite the pointer in `container` and cause a memory leak
        container->data = tmp;
        container->data_size++;
        container->data[container->data_size-1] = value;
    } else {
        // Handle allocation failure
    }

    len_data = container->data_size * sizeof(uint16_t);
    printf("LENGTH OF DATA: %d\n", len_data);
    printf("data_size: %d\n", container->data_size);

    return;
}
0 голосов
/ 27 августа 2018

void extend_data(data_t container, ...

В вашей функции container является не указателем, а самой структурой, переданной значением, поэтому вы не можете использовать оператор ->.

Перераспределенная память будет потеряна при работе с локальной копией переданной структуры и будет потеряна при возврате функции.

sizeof(*(container.data)) / sizeof(uint16_t)

это всегда будет 1, так как *(uint16_t *) / sizeof(uint16_t) всегда один.

Почему: data член является указателем на uint16_t. *data имеет тип uint16_t

sizeof вычисляется во время компиляции, а не во время выполнения, и не возвращает количество памяти, выделенное malloc.

...