Как обнулить новую память после перераспределения

Каков наилучший способ обнулить новую память после вызова realloc, сохранив при этом первоначально выделенную память?

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

size_t COLORCOUNT = 4;

typedef struct rgb_t {
    int r;
    int g;
    int b;
} rgb_t;

rgb_t** colors;

void addColor(size_t i, int r, int g, int b) {
    rgb_t* color;
    if (i >= COLORCOUNT) {
        // new memory wont be NULL
        colors = realloc(colors, sizeof(rgb_t*) * i);
       //something messy like this...
        //memset(colors[COLORCOUNT-1],0 ,sizeof(rgb_t*) * (i - COLORCOUNT - 1));

         // ...or just do this (EDIT)
        for (j=COLORCOUNT; j<i; j++) {
            colors[j] = NULL;
        }

        COLORCOUNT = i;
    }

    color = malloc(sizeof(rgb_t));
    color->r = r;
    color->g = g;
    color->b = b;

    colors[i] = color;
}

void freeColors() {
    size_t i;
    for (i=0; i<COLORCOUNT; i++) {
        printf("%x\n", colors[i]);
        // can't do this if memory isn't NULL
       // if (colors[i])
         //   free(colors[i]);

    }
}


int main() {
    colors = malloc(sizeof(rgb_t*) * COLORCOUNT);
    memset(colors,0,sizeof(rgb_t*) * COLORCOUNT);
    addColor(0, 255, 0, 0);
    addColor(3, 255, 255, 0);
    addColor(7, 0, 255, 0);


    freeColors();
    getchar();
}

person Nick Van Brunt    schedule 26.01.2010    source источник
comment
Это имеет очень низкую производительность, когда один цвет добавляется в конец списка цветов, обычный шаблон вызова. Вы будете добавлять только один элемент за раз перед перераспределением. Подумайте хотя бы о выделении max(i+1, COLORCOUNT * 2).   -  person Hans Passant    schedule 26.01.2010
comment
Это просто пример для иллюстрации проблемы. Фактическим источником является хеш-таблица, размер которой изменяется на простое число IIRC.   -  person Nick Van Brunt    schedule 26.01.2010


Ответы (4)


Вероятно, нет необходимости делать memset: возможно, вы не используете colors[k], прежде чем позже установить для него что-то действительное. Например, ваш код устанавливает colors[i] в новый выделенный указатель color, поэтому вам не нужно устанавливать colors[i] в NULL.

Но даже если вы хотите «обнулить его, чтобы все было хорошо» или действительно нужно, чтобы новые указатели были NULL: стандарт C не гарантирует, что all-bits-zero является константой нулевого указателя (т.е. NULL) , так что memset() в любом случае не является правильным решением.

Единственная портативная вещь, которую вы можете сделать, это установить каждый указатель на NULL в цикле:

size_t k;
for (k=COLORCOUNT; k < i+1; ++k) /* see below for why i+1 */
    colors[k] = NULL;

Ваша основная проблема в том, что ваш вызов realloc() неверен. realloc() возвращает указатель на память с измененным размером, он не (обязательно) изменяет ее размер на месте.

Итак, вы должны сделать:

/* i+1 because you later assign to colors[i] */
rgb_t **tmp = realloc(colors, (i+1) * sizeof *tmp);
if (tmp != NULL) {
    /* realloc succeeded, can't use colors anymore */
    colors = tmp;
} else {
    /* realloc failed, colors is still valid */
}

Если вы действительно хотите знать, каким должен быть вызов memset(), вам нужно обнулить память, начиная с colors+COLORCOUNT, и установить i+1-COLORCOUNT участников равными нулю:

memset(colors+COLORCOUNT, 0, (i+1-COLORCOUNT) * sizeof *colors);

Но, как я уже сказал выше, не гарантируется, что все нулевые байты будут указателем NULL, поэтому ваш memset() в любом случае бесполезен. Вы должны использовать цикл, если вам нужны указатели NULL.

person Alok Singhal    schedule 26.01.2010
comment
Спасибо - ошибка realloc была недосмотром при быстром написании теста, но я не понял, что все байты, равные нулю, не гарантируют NULL для всех элементов массива в соответствии со стандартом. - person Nick Van Brunt; 26.01.2010
comment
Сказать, что предположение NULL == 0 бессмысленно, — это немного преувеличение. Даже если это явно не требуется стандартом, это имеет место в каждом известном мне компиляторе C. Кроме того, использование calloc или memset для принудительного чтения с ошибками может значительно ускорить устранение ошибок. Я бы предпочел использовать NULL == 0 через assert(NULL == 0) в начале программы, чем писать тонну сложного и причудливого кода для поддержки одной случайной реализации, которая выбирает что-то другое. - person Dan Bechard; 26.12.2018
comment
@DanBechard NULL == 0 всегда верно. Таким образом, вам придется сделать assert примерно так: void * null_ptr = NULL; unsigned char * null_ptr_byte = &null_ptr; for (size_t i = 0; i < sizeof(null_ptr); ++i) assert(!null_ptr_byte[i]); - о, и вы не можете просто сделать memset(&null_ptr, 0, sizeof(null_ptr)); assert(!null_ptr);, потому что все нулевые биты вообще не должны быть допустимым представлением указателя, но могут быть представлением ловушки или иным образом вызывать неопределенное поведение, которое вы хотели предотвратить в первую очередь - в основном делая ваше утверждение бесполезным. - person Bodo Thiesen; 19.10.2020
comment
@BodoThiesen Если бы вы могли гарантировать, что NULL == 0 всегда верно, вам вообще не нужно было бы ничего утверждать. В этом-то и дело! - person Dan Bechard; 22.10.2020
comment
@DanBechard Целочисленное константное выражение со значением 0 или такое выражение, приведенное к типу void *, называется константой нулевого указателя. 66) Если константа нулевого указателя преобразуется в тип указателя, результирующий указатель, называемый нулевым указателем, гарантированно будет неравным при сравнении с указателем на любой объект или функцию. сноска: 66) Макрос NULL определен в ‹stddef.h› (и других заголовках) как константа нулевого указателя; см. 7.19. 6.3.2.3 (3) на стр. 55 стандарта ISO C (ISO/IEC 9899:2011) Обсуждаемая здесь проблема заключалась в том, что битовое представление не обязательно должно быть нулевым. - person Bodo Thiesen; 22.10.2020

Нет никакого способа решить это как общую закономерность. Причина в том, что для того, чтобы узнать, какая часть буфера является новой, вам нужно знать, как долго был старый буфер. Это невозможно определить в C и, следовательно, препятствует общему решению.

Однако вы можете написать такую ​​​​оболочку

void* realloc_zero(void* pBuffer, size_t oldSize, size_t newSize) {
  void* pNew = realloc(pBuffer, newSize);
  if ( newSize > oldSize && pNew ) {
    size_t diff = newSize - oldSize;
    void* pStart = ((char*)pNew) + oldSize;
    memset(pStart, 0, diff);
  }
  return pNew;
}
person JaredPar    schedule 26.01.2010

Прежде всего, realloc может дать сбой, поэтому вам нужно проверить наличие NULL. Во-вторых, нет лучшего способа обнулить память: просто memset от конца старого буфера до конца большего буфера.

person florin    schedule 26.01.2010
comment
мой закомментированный раздел приводит к сбою Visual Studio с помощью msvcr90d.dll!memset(unsigned char * dst=0x00000000, unsigned char value='', unsigned long count=2553828) Строка 103 Asm. Я ошибся на 1? - person Nick Van Brunt; 26.01.2010
comment
похоже, что вы передаете значение в colors[COLORCOUNT-1] для перераспределения, а не указатель на это место. - person atk; 26.01.2010
comment
realloc, возможно, не сможет перераспределить память на месте и вернет новый указатель. Вы должны проверить это и обновить цвет новым значением, если оно не равно NULL. Это одна из причин, по которой код может дать сбой. Другая причина может заключаться в том, что realloc не может выделить больше памяти (чтобы сообщить вам об этом, он вернул NULL, но вы его отбрасываете). - person Draemon; 26.01.2010
comment
Все нулевые байты могут быть или не быть константой нулевого указателя. Подробности смотрите в моем ответе. - person Alok Singhal; 26.01.2010

Напишите свою собственную функцию, скажем, reallocz, которая принимает текущий размер в качестве параметра и вызывает для вас realloc и memset. Это действительно не будет намного лучше, чем то, что у вас уже есть... в конце концов, это C.

person Thomas    schedule 26.01.2010