Хотя Жан-Франсуа Фабр ответил на заданный вопрос, я хотел бы отметить, что вы можете лучше управлять такими байтовыми массивами, используя структуру:
typedef struct {
size_t max; /* Number of chars allocated for */
size_t len; /* Number of chars in use */
unsigned char *data;
} bytearray;
#define BYTEARRAY_INIT { 0, 0, NULL }
void bytearray_init(bytearray *barray)
{
barray->max = 0;
barray->len = 0;
barray->data = NULL;
}
void bytearray_free(bytearray *barray)
{
free(barray->data);
barray->max = 0;
barray->len = 0;
barray->data = NULL;
}
Чтобы объявитьпустой байтовый массив, вы можете использовать либо bytearray myba = BYTEARRAY_INIT;
, либо bytearray myba; bytearray_init(&myba);
.Они эквивалентны.
Когда вам больше не нужен массив, вызовите bytearray_free(&myba);
.Обратите внимание, что free(NULL)
безопасен и ничего не делает, поэтому совершенно безопасно высвободить bytearray
, который вы инициализировали, но не использовали.
Чтобы добавить к bytearray
:
int bytearray_append(bytearray *barray, const void *from, const size_t size)
{
if (barray->len + size > barray->max) {
const size_t len = barray->len + size;
size_t max;
void *data;
/* Example policy: */
if (len < 8)
max = 8; /* At least 8 chars, */
else
if (len < 4194304)
max = (3*len) / 2; /* grow by 50% up to 4,194,304 bytes, */
else
max = (len | 2097151) + 2097153 - 24; /* then pad to next multiple of 2,097,152 sans 24 bytes. */
data = realloc(barray->data, max);
if (!data) {
/* Not enough memory available. Old data is still valid. */
return -1;
}
barray->max = max;
barray->data = data;
}
/* Copy appended data; we know there is room now. */
memmove(barray->data + barray->len, from, size);
barray->len += size;
return 0;
}
Поскольку эта функция, по крайней мере, теоретически может не перераспределить память, она вернет 0
в случае успеха и ненулевое значение, если не сможет перераспределить достаточно памяти.
Нет необходимости в вызове malloc()
потому что realloc(NULL, size)
в точности эквивалентно malloc(size)
.
«Политика роста» является очень спорным вопросом.Вы можете просто сделать max = barray->len + size
и покончить с этим.Однако функции динамического управления памятью относительно медленны, поэтому на практике мы не хотим вызывать realloc()
для каждого небольшого небольшого добавления.
Приведенная выше политика пытается сделать что-то лучше, но не слишком агрессивно:он всегда выделяет как минимум 8 символов, даже если требуется меньше.До 4 194 304 символов, он выделяет 50% дополнительно.Кроме того, он округляет размер распределения до следующего кратного 2 097 152 и вычитает 24. Обоснование этого сложное, но оно больше для иллюстрации и понимания, чем что-либо еще;это определенно НЕ «это лучше, и это то, что вы должны делать тоже» .Эта политика гарантирует, что каждый байтовый массив выделяет не более 4 194 304 = 2 22 * 1034 * неиспользуемых символов.Однако 2 097 152 = 2 21 - это размер огромной страницы в AMD64 (x86-64), и это кратное по размеру собственное число страниц по сравнению с собственным размером страницы практически на всех архитектурах.Он также достаточно велик, чтобы переключаться с так называемого выделения sbrk () на отображение памяти практически на всех архитектурах, которые это делают.Это означает, что такие огромные выделения используют отдельную часть кучи для каждого, и неиспользуемая часть обычно является просто виртуальной памятью, не обязательно поддерживаемой какой-либо оперативной памятью до тех пор, пока к ней нет доступа.В результате, эта политика имеет тенденцию работать довольно хорошо как для очень коротких байтовых массивов, так и для очень длинных байтовых массивов, на большинстве архитектур.
Конечно, если вы знаете (или измерьте!) Типичный размер байтовых массивов в типичных рабочих нагрузках, вы можете оптимизировать политику роста для этого и получить еще лучшие результаты.
Наконец, он использует memmove()
вместо memcpy()
на тот случай, если кто-то захочет повторить часть одного и того же байтового массива: memcpy()
работает только в том случае, если исходная и целевая области не перекрываются;memmove()
работает даже в этом случае.
При использовании более сложных структур данных, таких как хеш-таблицы, часто бывает полезен вариант вышеуказанной структуры.(То есть это намного лучше в тех случаях, когда у вас много пустых байтовых массивов.)
Вместо указателя на данные данные являются частью самой структуры, как член гибкого массива C99:
typedef struct {
size_t max;
size_t len;
unsigned char data[];
} bytearray;
Вы не можете объявить сам байтовый массив (т.е. bytearray myba;
не будет работать);Вы всегда объявляете указатель на такие байтовые массивы: bytearray *myba = NULL;
.Указатель, равный NULL, просто обрабатывается так же, как пустой байтовый массив.
В частности, чтобы увидеть, сколько элементов data
имеет такой массив, вы используете функцию доступа (также определенную в том же заголовочном файле).в качестве структуры данных), а не myba.len
:
static inline size_t bytearray_len(bytearray *const barray)
{
return (barray) ? barray->len : 0;
}
static inline size_t bytearray_max(bytearray *const barray)
{
return (barray) ? barray->max : 0;
}
(expression) ? (if-true) : (if-false)
является троичным оператором.В этом случае первая функция в точности эквивалентна
static inline size_t bytearray_len(bytearray *const barray)
{
if (barray)
return barray->len;
else
return 0;
}
Если вам интересно узнать о bytearray *const barray
, помните, что объявления указателей читаются справа налево, с *
как «указатель на». Таким образом, это просто означает, что barray
является константой, указателем на байтовый массив. То есть мы можем изменить данные, на которые он указывает, но мы не будем менять сам указатель. Компиляторы обычно могут обнаружить такие вещи сами, но это может помочь; однако главное - напомнить нам, программистам-людям, что указатель не должен быть изменен. (Такие изменения будут видны только внутри самой функции.)
Поскольку размер таких массивов часто нужно изменять, изменение размера часто помещается в отдельную вспомогательную функцию:
bytearray *bytearray_resize(bytearray *const barray, const size_t len)
{
bytearray *temp;
if (!len) {
free(barray);
errno = 0;
return NULL;
}
if (!barray) {
temp = malloc(sizeof (bytearray) + len * sizeof barray->data[0]);
if (!temp) {
errno = ENOMEM;
return NULL;
}
temp->max = len;
temp->len = 0;
return temp;
}
if (barray->len > len)
barray->len = len;
if (barray->max == len)
return barray;
temp = realloc(barray, sizeof (bytearray) + len * sizeof barray->data[0]);
if (!temp) {
free(barray);
errno = ENOMEM;
return NULL;
}
temp->max = len;
return temp;
}
Что это errno = 0
делает там? Идея состоит в том, что, поскольку изменение размера / перераспределение байтового массива может изменить указатель, мы возвращаем новый. Если распределение завершится неудачно, мы вернем NULL
с errno == ENOMEM
, как malloc()
/ realloc()
do. Однако, поскольку требуемая новая длина была нулевой, это экономит память, освобождая старый байтовый массив, если таковой имеется, и возвращает NULL
. Но поскольку это не ошибка, мы устанавливаем errno
в ноль, чтобы вызывающим абонентам было проще проверить, произошла ошибка или нет. (Если функция возвращает NULL
, проверьте errno
. Если errno
не равен нулю, произошла ошибка; вы можете использовать strerror(errno)
, чтобы получить описательное сообщение об ошибке.)
Вы, вероятно, также отметили sizeof barray->data[0]
, используемый даже тогда, когда barray
НЕДЕЙСТВИТЕЛЕН. Это нормально, потому что sizeof
не функция, а оператор: он вообще не имеет доступа к правой стороне, он только оценивает размер того, к чему относится правая сторона. (Вы должны использовать круглые скобки, только когда правильный размер является типом.) Эта форма хороша, потому что она позволяет программисту изменять тип элемента data
, не изменяя никакого другого кода.
Чтобы добавить данные в такой байтовый массив, мы, вероятно, хотим иметь возможность указать, ожидаем ли мы дальнейшее добавление к тому же массиву или это, вероятно, последнее добавление, так что требуется только точно необходимый объем памяти , Для простоты я буду реализовывать только точный размер версии здесь. Обратите внимание, что эта функция возвращает указатель на (модифицированный) байтовый массив:
bytearray *bytearray_append(bytearray *barray,
const void *from, const size_t size,
int exact)
{
size_t len = bytearray_len(barray) + size;
if (exact) {
barray = bytearray_resize(barray, len);
if (!barray)
return NULL; /* errno already set by bytearray_resize(). */
} else
if (bytearray_max(barray) < len) {
if (!exact) {
/* Apply growth policy */
if (len < 8)
len = 8;
else
if (len < 4194304)
len = (3 * len) / 2;
else
len = (len | 2097151) + 2097153 - 24;
}
barray = bytearray_resize(barray, len);
if (!barray)
return NULL; /* errno already set by the bytearray_resize() call */
}
if (size) {
memmove(barray->data + barray->len, from, size);
barray->len += size;
}
return barray;
}
На этот раз мы объявили bytearray *barray
, потому что мы изменили, куда barray
указывает в функции. Если четвертый параметр, final
, не равен нулю, то результирующий байтовый массив будет точно необходимого размера; в противном случае применяется политика роста.