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

Кой е най-добрият начин за нулиране на нова памет след извикване на 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 не гарантира, че всички битове-нула е константата на нулев указател (т.е. 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
моята коментирана секция срива визуално студио с 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
изглежда, че предавате стойността в цветове [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