Приведет ли это к утечке памяти?

#c #memory #pointers #memory-leaks #allocation

#c #память #указатели #утечки памяти #распределение

Вопрос:

Хорошо, итак, у меня возникли некоторые разногласия с кем-то еще, и я надеялся, что кто-то, кто знает о c больше, чем любой из нас, сможет это прояснить. Допустим, у нас есть этот блок кода где-то внутри функции (для движка tilemap):

 void loadTiles()
{
Tile* tile = new Tile();
Level->addTile(x, y, tile); //x and y are just arbitrary ints.
/* when addTile is called, it fills the values of the chunk of memory pointed to by tile to the predefined chunk of memory created in the Level object. */
//Then, to remove the dangling pointer safely,
tile = NULL;
} //Then the actual memory pointed to by tile is deallocated here.
  

Класс Level имеет 2D массив плиток, называемый map[][], и его функция addTile выглядит точно так:

 void Level::addTile(int x, int y, Tile *tile) 
{
    map[x][y] = tile;
}
  

Память, указанная на tile, была освобождена, указатель больше не указывает на несуществующий объект, а значения для объекта tile были по существу скопированы в массив объекта уровня map[][] . Я прав, или я ошибаюсь? Другой человек утверждает, что это приведет к утечке памяти.

Комментарии:

1. Утечка памяти или нет, по представленному вами коду невозможно определить, и это делает его плохим кодом . Вам следует провести рефакторинг этого, чтобы было более очевидно, что происходит с памятью, для которой вы выделяете *tile . Если вы не используете память в локальной области, нет абсолютно никаких причин выделять ее в этой области. Разместите его внутри addTile .

2. Где находится соответствующее delete для new Tile() ? Действительно ли map Tile[][] или на самом деле a Tile*[][] ?

3. Покажите нам, как вы инициализируете и, что более важно, как вы уничтожаете map, и мы скажем вам, является ли это утечкой памяти или нет. Потому что прямо сейчас вы просто копируете указатель на созданный объект Tile в array, но что произойдет дальше, никто не знает.

4. Если вы запускаете эту программу в системе UNIX, не следует ли вам использовать valgrind для обнаружения подобных утечек памяти? В системе Windows проверка утечки памяти также может быть включена в Visual Studio.

5. «Утечка памяти или нет, по представленному вами коду невозможно определить, и это делает его плохим кодом «. 1. Это плохой дизайн, и вопросы, которые задавал Keelx в другом месте, подтверждают, что это плохой код (а также подтверждают, что это утечка). Level Объекту навязывается ресурс. Как Level объект узнает, что навязываемый ему ресурс необходимо удалить?

Ответ №1:

Давайте рассмотрим каждый фрагмент кода.

1) Выделение памяти

 Tile* tile = new Tile();
  

Это создает новый объект Tile в куче и сохраняет адрес памяти в переменной tile. Имейте в виду, что переменная tile — это всего лишь указатель, а не сам объект.

2) Копирование ссылки

 void Level::addTile(int x, int y, Tile *tile) { map[x][y] = tile;}
  

Вышеупомянутая функция просто принимает указатель и сохраняет его в многомерный массив для дальнейшего использования. В контексте всего перечисленного кода теперь будет ДВЕ ссылки на объект…фрагмент * в исходной вызывающей функции и запись в многомерном массиве. Опять же, имейте в виду, что это указатели (всего 4 байта, в зависимости от архитектуры вашей системы).

3) Установка указателя на NULL

 tile = NULL;
  

Результатом этого кода является то, что переменная-указатель tile больше не будет указывать на созданный объект в куче. Тем не менее, объект все еще будет существовать. На этом этапе, в контексте всего кода, у вас все еще будет указатель на объект из-за массива map[][] .

На самом деле вам не нужна эта строка кода. tile не является висячим указателем, поскольку он указывает на допустимый объект (до того, как вы присвоите ему значение NULL). Код также не уничтожает объект. Поскольку tile является локальной переменной, она будет очищена при выходе из области действия функции. Очищен будет только указатель, а не объект, на который он указывает. Установка значения NULL в этом сценарии дает очень мало результатов, за исключением циклов отходов.

Это также не утечка памяти, скажем так. У вас все еще есть действительный указатель на объект в массиве map[][], поэтому вы всегда можете использовать эту ссылку для очистки памяти.

4) Что вам нужно сделать

Вам нужно где-нибудь удалить объект. В C это ключевое слово delete.

 delete tile;
  

или

 delete map[x][y];
  

Теперь имейте в виду, что после выполнения приведенного выше кода память в куче будет освобождена обратно для операционной системы, и ваше приложение больше не сможет безопасно обращаться к памяти. Следовательно, любой вызов map[x][y]->{someMethod} приведет к исключению с нарушением доступа. Указатель, хранящийся в map[x] [y], теперь является висячим указателем (он указывает на адрес памяти, который недопустим для этого типа).

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

 void Level::deleteTile(int x, int y) 
{ 
    if (map[x][y] != NULL)
    {
        delete map[x][y];
        map[x][y] = NULL;
     }
}
  

Я бы также изменил метод addTile на что-то вроде этого:

 void Level::addTile(int x, int y, Tile *tile)
{
    deleteTile(x, y);
    map[x][y] = tile;
}
  

Наконец, когда вы удаляете объект уровня, вам нужно будет сделать что-то вроде этого:

 void ~Level()
{
    for (int i; i<MaxX; i  )
    {
        for (int j; j<MaxY; j  )
        {
            delete map[i][j];
        }
    }
}
  

Когда вы создаете объект уровня, вы должны обнулить массив map[][], чтобы все значения также были равны NULL. Поскольку массив map не является локальным для функции, хорошей идеей будет присвоить всем значениям его указателя значение NULL, чтобы вы знали, когда он содержит допустимый указатель, а когда нет.

Если вы динамически создаете (используя ключевое слово new) массив map[][], вам также потребуется очистить его память с помощью ключевого слова delete.

Надеюсь, это поможет.

Комментарии:

1. Очень хорошее объяснение, но я включил фигурные скобки, чтобы указать область действия. Не будет ли Tile * tile автоматически удален в конце указанной области?

2. В этом разница между статически распределяемыми переменными и динамически распределяемыми переменными. Если вы используете ключевое слово new (динамическое распределение), вы всегда должны явно удалять их где-нибудь. Ключевое слово new создаст объект в куче и вернет указатель. Указатель будет очищен при выходе из области видимости, но не объект, на который он указывал.

3. Обратите внимание, что вам не нужно удалять объект в том же методе / области видимости, в которой вы его выделили. Например, вам может понадобиться метод, который создает объект, выполняет с ним некоторую инициализацию, затем возвращает указатель на объект. Затем вы можете сохранить этот возвращенный указатель и использовать его для последующего удаления фактического объекта (это означает, что вам не нужно удалять исходный указатель; подойдет любой указатель, который указывает на ту же ячейку памяти).

Ответ №2:

Это приведет к утечке памяти. В опубликованной области видимости нет абсолютно никакого кода, который уничтожил бы объект Tile, который вы выделили динамически. Вы никуда не копируете никаких значений — все они являются указателями, которые являются ссылками. Также нет очевидной причины, по которой вы выполняли динамическое распределение, и если, как вы говорите, map это Tile[][] , то я поражен, что это вообще компилируется.

Ответ №3:

Хрестоматийный пример утечки, если вы не сделаете delete этого map позже. И нет, память, конечно, не освобождена. С чего бы это? Кроме того, вы не перемещаете указатель или что-либо еще, после вызова addTile указатель tile остается действительным.

Ответ №4:

Нет, это в основном передает право собственности от tile к map[x][y] объекту Level . Вы должны убедиться, что указанный объект будет освобожден позже (т. Е. в деструкторе Level . Вам также следует изменить свой addTile() метод, чтобы проверить, что map[x][y] он ни на что не указывает, иначе произойдет утечка памяти, на которую он ранее указывал.

Ответ №5:

Память, указанная на tile, была освобождена, указатель больше не указывает на несуществующий объект, а значения для объекта tile были по существу скопированы в массив объекта уровня map[][] .

Извините, но вы ошибаетесь по каждому из своих пунктов.

  • «Память, на которую указывает [by] tile, была освобождена.

Общее правило таково: у каждого new должно быть ровно одно совпадение delete . Поскольку вы выделили память с помощью new и никогда не вызывали delete , память никогда не освобождается.

  • «Указатель больше не указывает на несуществующий объект»

Переменная, вызываемая tile в анонимном фрагменте кода, указывает на выделенную память до тех пор, пока NULL ей не будет назначена. Переменная, вызываемая tile в Level::addTile , указывает на выделенную память на весь ее срок службы.

  • «значения для объекта tile были по существу скопированы в массив объекта Level map[][]»

Было скопировано значение указателя, а не значения объекта.

Следующий фрагмент кода выполняет то, что, по вашему мнению, делал ваш код:

 {
  Tile tile;
  Level->addTile(x, y, tile); //x and y are just arbitrary ints.
  /* when addTile is called, it fills the values of the chunk of memory pointed to by tile to the predefined chunk of memory created in the Level object. */
} //Then the actual memory pointed to by tile is deallocated here.

void Level::addTile(int x, int y, Tile amp;tile) 
{
    map[x][y] = tile;
}
  

Ответ №6:

Я никоим образом не являюсь экспертом по управлению памятью на C , но я бы сказал, что то, что вы делаете, не приведет к утечкам памяти (учитывая, что вы правильно удаляете память позже где-нибудь). Я аргументирую это тем, что у вас все еще есть действительный указатель на выделенную вами память, следовательно, вы все еще можете удалить is, используя ключевое слово ‘delete’.

Думайте об указателях как об адресах памяти. Когда вы выделяете память с помощью ‘new’, вы должны убедиться, что вы где-то сохраняете этот адрес, чтобы вы действительно могли получить доступ к данным. Адрес хранится в /как указатель. Итак, если вы потеряете свой указатель (выйдя за пределы области видимости, как в вашем примере с блоком кода), вы потеряете возможность освободить выделенную память, потому что вы больше не можете знать, где находятся данные в памяти. Однако в вашем примере вы все еще сохраняете указатель в своем массиве ‘map’, так что вы все равно сможете освободить выделенную память.

Здесь важно помнить, что указатель — это НЕ сами данные. Потеря указателя не так уж плоха, пока у вас все еще есть другой указатель на данные. Как только вы потеряете ВСЕ свои указатели на данные, вы направляетесь в область утечки памяти.

Ответ №7:

Это выделяет объект в куче :

 Tile* tile = new Tile();
  

Это ничего не дает :

 tile = NULL;
} //Then the actual memory pointed to by tile is deallocated here.
  

Поскольку объект не удален, это утечка памяти.

Ответ №8:

Создайте map 2D массив указателей на Tile .

Затем, когда вы будете готовы освободить ресурсы, delete все элементы в map , а затем удалите саму карту (если она выделена с помощью new ).

Таким образом, вы не допустите утечки памяти.

Ответ №9:

В опубликованном вами коде освобождение не выполняется. Вы выделяете новый объект Tile, и реализация addTile() становится владельцем этого.

Утечки памяти как таковой нет; это зависит от реализации класса Level, который содержит выделенный объект Tile. Если в какой-то момент это delete объекты tile из внутренней карты, то утечки памяти нет, в противном случае она есть.