Вы не правильно рассчитали новый размер. Учтите это:
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 необходима для метаданных выделения.)