C Использование памяти в связанном списке (возможная утечка памяти) - PullRequest
0 голосов
/ 05 марта 2010

У меня проблемы с тем, что должно быть простой программой.

Я написал реализацию единого связанного списка на C, используя void* указатели. Однако у меня есть проблема, поскольку где-то возможна утечка памяти, однако я проверил код с помощью valgrind, и он не обнаружил таких ошибок.

Но когда вся память free d, часть памяти все еще не освобождена (см. Комментарии) ... Я тоже пытался передать все функции add по ссылке, но это тоже не решило проблему .

Мне просто интересно, есть ли у кого-нибудь здесь комментарии по поводу просмотра кода. (Это должно быть просто !, верно?)

/*
 Wrapping up singley linked list inside a struct
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h> /* Needed for: memcpy */

void waitKey(){
 printf("Press any key to continue...");
 getchar();
}

/* Define a structure for a list entry */
struct ListEntry {
 void* data;
 struct ListEntry* pNext;
};

/* Struct for list properties */
struct ListProperties {
 struct ListEntry* g_pLast;
 struct ListEntry* g_pHead;
 struct ListEntry* pCurrent;
 unsigned int size;
 int getHead;
};

/* Add:
 args: list, data, dyn (0 if not, else size of dynamic data)
*/
void add(struct ListProperties* l, void* d, unsigned long dyn) {
 struct ListEntry* pNew = malloc(sizeof(struct ListEntry));

 /* Set the data */
 if (dyn > 0){
  /* Allocate and copy array */
  pNew->data = malloc(dyn);
  pNew->data = memcpy(pNew->data,d,dyn);
 } else {
  pNew->data = d;
 }

 /* Set last element to point to new element */
 if (l->g_pLast != NULL){
  l->g_pLast->pNext = pNew;

  /* Get head of list */
  if (l->g_pHead == NULL && l->getHead == 0){
   l->g_pHead = l->g_pLast;
   l->getHead = 1;
  }
 } else {
  /* 1 elem case */
  l->g_pHead = pNew;
  l->pCurrent = pNew;
 }

 /* New element points to NULL */
 pNew->pNext = NULL;

 /* Save last element for setting 
    pointer to next element */
 l->g_pLast = pNew;

 /* Inc size */
 l->size++;
}

/* Create new list and return a pointer to it */
struct ListProperties* newList(){
 struct ListProperties* nList = malloc (sizeof(struct ListProperties));
 nList->g_pHead = NULL;
 nList->g_pLast = NULL;
 nList->getHead = 0;
 nList->size = 0;
 return nList;
}

/* Reset pointer */
int reset(struct ListProperties *l){
 if (l->g_pHead != NULL){
  l->pCurrent = l->g_pHead;
  return 0;
 }
 return -1;
}

/* Get element at pointer */
void* get(struct ListProperties *l) {
 if (l->size > 0){
  if (l->pCurrent != NULL){
   return l->pCurrent->data;
  }
 }
 return NULL;
}

/* Increment pointer */
int next(struct ListProperties *l){
 if (l->pCurrent->pNext != NULL){
  l->pCurrent = l->pCurrent->pNext;
  return 1;
 }
 return 0;
}

/* Get element at n */
void* getatn(struct ListProperties *l, int n) {
 if (l->size > 0){
  int count = 0;
  reset(l);
  while (count <= n){
   if (count == n){
    return l->pCurrent->data;
    break;
   }
   next(l);
   count++; 
  }
 }
 return NULL;
}

/* Free list contents */
void freeList(struct ListProperties *l){
 struct ListEntry* tmp;

 /* Reset pointer */
 if (l->size > 0){
  if (reset(l) == 0){
   /* Free list if elements remain */
   while (l->pCurrent != NULL){
    if (l->pCurrent->data != NULL)
     free(l->pCurrent->data);
    tmp = l->pCurrent->pNext;
    free(l->pCurrent);
    l->pCurrent = tmp;
   }
  }
 }

 l->g_pHead = NULL;
 l->g_pLast = NULL;

 l->size = 0;
 l->getHead = 0;

 free(l);
}

void deleteElem(struct ListProperties *l, int index){
 struct ListEntry* tmp;
 int count = 0;
 if (index != 0)
  index--;
 reset(l);
 while (count <= index){
  if (count == index){ // Prev element
   if (l->pCurrent != NULL){
    if (l->pCurrent->pNext != NULL){
     free(l->pCurrent->pNext->data); // Free payload
     tmp = l->pCurrent->pNext;
     l->pCurrent->pNext = l->pCurrent->pNext->pNext;
     free(tmp);
     if (l->size > 0)
      l->size--;
    } else {
     // Last element
     free(l->pCurrent->data);
     free(l->pCurrent);
     l->g_pHead = NULL;
     l->g_pLast = NULL;
     l->getHead = 0;
     l->size = 0;
    }
   }
   break;
  }
  if (next(l) != 1)
   break;
  count++;
 }
}

int size(struct ListProperties *l){
 return l->size;
}

int main( int argc, char* argv )
{
 int j = 0;
 unsigned long sz = 0;

 /*=====| Test 1: Dynamic strings |=====*/

 /* Create new list */
 struct ListProperties* list = newList();

 if (list == NULL)
  return 1;

 char *str;
 str = malloc(2);
 str = strncat(str,"A",1);
 sz = 2;

 printf("Dynamic Strings\n===============\n");

 /* Check memory usage here (pre-allocation) */
 waitKey();

 /* Add to list */
 for (j = 0; j < 10000; j++){
  add(list,(char*)str, sz);
  str = realloc(str, sz+2);

  if (str != NULL){
   str = strncat(str,"a",1);
   sz++;
  }
 }

 /* Allocated strings */
 waitKey();

 /* TESTING */
 freeList(list);
 free(str);

 /* Check memory usage here (Not original size!?) */
 waitKey();
 return 0;
}

Спасибо!

Ответы [ 3 ]

3 голосов
/ 05 марта 2010

Вы не говорите, как вы проверяете использование памяти, но я собираюсь догадаться, что вы используете ps или что-то подобное, чтобы увидеть, сколько памяти ОС выделило процессу.

В зависимости от вашего распределителя памяти, вызов free может возвращать или не возвращать память в ОС. Поэтому, даже если вы звоните free, вы не увидите уменьшения объема памяти с точки зрения ОС.

Распределитель может хранить кэш памяти, предоставленный ему ОС. Вызов malloc сначала заглянет в этот кеш, чтобы выяснить, может ли он найти достаточно большой блок, и, если это так, malloc может вернуться, не запрашивая у ОС больше памяти. Если он не может найти достаточно большой блок, malloc запросит у ОС больше памяти и добавит его в кеш.

Но free может просто добавить память обратно в кеш и никогда не вернуть ее в ОС.

Итак, вы можете видеть кеш распределителей, а не утечку памяти.

0 голосов
/ 06 марта 2010

Ваша программа содержит неверный argv в основном, неверное использование strncat и странное распределение памяти. Некоторые из них должны отображаться как предупреждения. Argv не является проблемой, но если другие появились в качестве предупреждения, вам нужно прислушаться к ним. Не игнорируйте предупреждения.

Эти изменения очищают его. Самым большим было то, что вы, кажется, плохо разбираетесь в символе NUL ('\ 0') (отличающемся от NULL указатель ), используемом для завершения строк C, и как это влияет 1008 *.

Смешанное использование функций str * с функциями памяти (* alloc / free), вероятно, было частью путаницы. Быть осторожен.

 #include <assert.h>
 ...
 int main( int argc, char* argv[] )       /* or int main(void) */
 ...
 sz = 2;
 str = (char*) malloc(sz);       /* allocate 2 bytes, shortest non-trivial C string */
 assert(str != NULL);
 strncpy(str, "A", sz);          /* copy 'A' and '\0' into the memory that str points to */
 ...
/* Add to list */
 for (j = 0; j < 10000; j++){
  add(list, str, sz);
  str = realloc(str, ++sz);      /* realloc str to be one (1) byte larger */
  assert(str != NULL);

  strncat(str, "a", sz - strlen(str));    /* now insert an 'a' between last 'A' or 'a' and '\0' */
  assert(str != NULL);
 }
0 голосов
/ 06 марта 2010

Как уже упоминалось, я бы не стал доверять использованию памяти, сообщаемому диспетчером задач, поскольку на него влияют и другие факторы, не зависящие от вас (как реализованы malloc / free и т. Д.).

Один из способовМожно проверить утечки памяти, написав свои собственные функции-оболочки вокруг существующих функций malloc и free, аналогичные:

void* my_malloc(size_t len) {
    void* ptr = malloc(len);
    printf("Allocated %u bytes at %p\n", len, ptr);
    return ptr;
}

void my_free(void* ptr) {
    printf("Freeing memory at %p\n", ptr);
    free(ptr);
}

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

...