0 / 0 / 0
Регистрация: 25.07.2020
Сообщений: 36
|
||||||
1 | ||||||
Ошибка при создании и заполнении динамического массива указателей на объекты23.03.2022, 21:09. Показов 1137. Ответов 16
Коллеги, пишу что-то типа простенького симулятора карточной игры с активным использованием указателей и new\delete конструкций (для тренировки).
Есть вот такой код: Кликните здесь для просмотра всего текста
Суть в чём вкратце: 1. Есть класс CARD (объектов типа карта с полями достоинство и масть). Он умеет только создавать карту через различные к-торы и выводить на экран. 2. Есть класс DECK (колода из 52 карт), который представлен массивом из 52 указателей на объекты типа CARD. Он умеет инициализировать объект Колоды с помощью new CARD() к-тора т.е. заполнять колоды картами по порядку. Также умеет выводить всю колоду на экран, мешать 2 произвольные карты, а также перемешивать всю колоду. Это реализовано через перемешивание указателей. Ещё умеет вытаскивать одну карту из колоды по номеру, возвращая указатель на вытащенную карту и, соответственно, убирая её из колоды. До этого момента всё работает, как часы! 3. Дальше сделал некий класс SUBSET (произвольное кол-во карт извлекаемых из объекта колоды класса DECK). В нём сидит указатель типа CARD** т.е. pointer-to-pointer на начало массива, содержащего указатели на объекты типа CARD, а также размер выделяемого подмножества карт - size (size будет задан в к-торе объекта). К-тор этого класса с числовым параметром инициализирует поле size, а затем динамически создаёт через конструкцию new Card*[size] массив указателей на некоторое подмножество карт (скажем 6). К-тор только выделяет память, но не заполняет его указателями на реальные карты! Дальше функция данного класса SUBSET::fill(DECK), которая принимает на вход ранее созданный объект колоды, должна заполнить созданный в к-торе SUBSET массив указателей на карты. Пока что беру для заполнения объекта подмножества просто первые карты подряд из перемешанной колоды т.е. без изысков. Всё вроде заполняется и потом выводится на экран с помощью метода SUBSET::dislplay(), ОДНАКО, уже после корректного отображения всех данных появляется ошибка: "Debug Assertion Failed! (Expression:_CtrlsValidHeapPointer(Block))." Экспериментальным путём с помощью _getch() после каждой "переброшенной" из колоды в подмножество карты установил, что ошибка возникает в методе SUBSET::fill() после внесения последнего э-та. Вроде размерности все соответствуют т.е. скажем выделяю место под массив указателей на 6 карт, дальше ровно 6 указателей из объекта колоды туда и отправляю, но ошибка не уходит... Я явно, что то напутал либо с new, либо с delete, но и так и так пробовал и в к-торах и деструкторах и в fill() шаманил, никак не могу это отловить или как то исправить! Подскажите, пожалуйста, где у меня ошибка в коде и заодно соориентируйте правильно ли я использую delete в классах DECK и SUBSET для освобождения памяти?
0
|
23.03.2022, 21:09 | |
Ответы с готовыми решениями:
16
Ошибка при заполнении динамического массива Ошибка при заполнении динамического массива
|
0 / 0 / 0
Регистрация: 25.07.2020
Сообщений: 36
|
||||||
24.03.2022, 13:46 [ТС] | 3 | |||||
Это поправил. Не знал, что их надо так "занулять" при выделении памяти, ибо мы же сразу в fill() в них копируем указатели из объекта deck т.е. они неопределёнными вроде не остаются.
А как можно обнулить указатель ПОСЛЕ его удаления? Это согласен, спасибо! Пропустил, что лучше объект в функцию передавать по ссылке! К сожалению та же ошибка Debug Assertion по-прежнему появляется при завершении программы т.е. объект sub нормально заполняется картами из deck, и deck и sub корректно выводятся на экран, но УВЫ, то ли при выходе из main, то ли в процессе освобождения какой то памяти выскакивает этот неприятный error! Добавлено через 14 минут UPDATE1 Оказывается, я с момента задания вопроса в процессе отладки закомментил строку в определении метода CARD* DECK::drawCrd(int i)
Так вот, когда строку раскоменчиваешь, ошибка исчезает, но если мы её оставляем не работающей (т.е. указатели на карты уже скопировали в подмножество, но сами указатели на эти же карты исходной колоды оставляем без изменения, как будто колода не изменилась), этот баг опять начинает выскакивать. Т.е. при исключении карт из колоды всё ОК и в колоде и в подмножестве. А при копировании в подмножество с оставлением в колоде Debug Assertion Failure. Логики вообще не понимаю.
0
|
![]() |
|
24.03.2022, 13:59 | 4 |
![]() Решение
два указателя на одну область памяти и два деструктора (дэк и сабсет) по очереди делают delete этих указателей
1
|
0 / 0 / 0
Регистрация: 25.07.2020
Сообщений: 36
|
||||||
24.03.2022, 14:17 [ТС] | 5 | |||||
Ага! Так что нужно убрать один деструктор, например из сабсета, в случае, если решаем оставить "вытянутые" карты в колоде? Или как это исправить? Мне для понимания, поскольку путаюсь в теме освобождения памяти.
Добавлено через 12 минут Ну да, мой косяк! Получается, поскольку мы в Deck и так в конце программы все указатели на карты удаляем через,
Правда непонятно почему, если мы ту часть указателей, которые из Deck присвоили элементам Subset предварительно в Deck обнулили, то тогда деструктор Subset, который работает после или одновременно с дестр. Deck эту ошибку уже не вызывает, ведь мы всё равно пытаемся второй раз сделать delete тех областей памяти, которые уже освободили первым деструктором Deck?
0
|
фрилансер
![]() 6327 / 5459 / 1108
Регистрация: 11.10.2019
Сообщений: 14,523
|
|
24.03.2022, 15:40 | 6 |
realalexandro,
std::unique_ptr<CARD> в помощь ![]() Добавлено через 50 секунд и std::vector
0
|
![]() 90 / 60 / 32
Регистрация: 06.08.2020
Сообщений: 220
|
|
24.03.2022, 15:57 | 7 |
Нюанс в том, что delete не удаляет указатель, а только освобождает память. Полезно после делета его занулять. К тому же это помогает потом в деструкторе делать проверку указателя на нуль перед делетом. Благодаря этому не возникает ситуации с попыткой двойного делета уже освобожденной памяти.
1
|
![]() |
|
24.03.2022, 16:05 | 8 |
0
|
0 / 0 / 0
Регистрация: 25.07.2020
Сообщений: 36
|
|
24.03.2022, 19:09 [ТС] | 9 |
Так это где мне его надо обнулить получается после удаления - и в объекте DECK и в объекте SUBSET?
0
|
![]() 90 / 60 / 32
Регистрация: 06.08.2020
Сообщений: 220
|
||||||
25.03.2022, 13:46 | 10 | |||||
realalexandro, drwCrd я бы написал вообще по другому. тут ведь надо и указатель очистить, и что-то вернуть. Уж лучше тогда ничего не возвращать. Хотя возможны варианты.
Потом эти goto, первый раз их увидел в коде ![]() Не понял зачем в subset двойной указатель, но я вообще слабо в них ориентируюсь. В общем, немного переписал код на свой вкус:
1
|
0 / 0 / 0
Регистрация: 25.07.2020
Сообщений: 36
|
|||||||||||
25.03.2022, 15:03 [ТС] | 11 | ||||||||||
PencilTorch, Спасибо за труд во-первых! Правда, "идеология" кода изменилась до неузнаваемости, но это, как говорится: "... как художник художнику..."
![]() 1. По поводу наследования. Класс SUBSET как раз задумывался как базовый, так что оно будет. Двойной указатель Card** subset был нужен затем, что это указатель на массив УКАЗАТЕЛЕЙ, каждый из которых в свою очередь указывает на конкретную карту (А НЕ ПРОСТО УКАЗАТЕЛЬ НА МАССИВ ГОТОВЫХ КАРТ!) т.е. это примерно то же самое, что и массив указателей на карты CARD* deck[52] в классе DECK в моём коде. Просто т.к. в колоде всегда 52 карты гораздо нагляднее так объявлять, что будет 52 указателя, чем просто объявлять указатель на массив карт CARD* deck = nullptr; и вводить переменную размерность unsigned short SIZE;, которая по сути не меняется. Я решил делать колоду именно указателями на карты, а не самими объектами карт, чтобы было быстрее их мешать (т.к. не понадобится копирование целых объектов при перестановках) т.е. в моей парадигме и в колоде и в SUBSET все действия должны происходить не с самими картами, которые раз и навсегда созданы в памяти(до срабатывания дест-ра), а с указателями на них т.е. с их адресами... 2. По поводу goto, его разумеется лучше вообще не использовать, но в данном случае т.к. это касается только локальной проверки того, что случайно генерируемые номера индексов массива CARD* deck[52] при перемешивании не должны совпадать, а если они случайно совпали, то сразу генерится новая пара, думаю, нормально. 3. CARD* DECK::drawCrd(int i) сначала копирует по индексу указатель во временную переменную, потом только зануляет соответствующий указатель в DECK и возвращает тот временный сохр. указатель т.е. мы получаем на выходе адрес карты, которую "вытащили" из колоды, он попадает в SUBSET, и одновременно из массива DECK мы его убираем т.к. одна и та же карта не может быть и на руках и оставаться в колоде, если это не шулер-казино ![]() 4. Проблему избежания "вторичного" удаления в дест-ре SUBSET тех объектов карт, которые уже могли быть удалены в рамках дестр-ра класса DECK (или наоборот), я решил путём добавления в каждую карту int поля count_ptrs в котором считается, сколько указателей в данный момент указывают на данную карту. Удаление в дест-ре каждой карты будет происходить только, когда остаётся только один указатель на данный конкретный объект. В противном случае просто уменьшаю счётчик числа указателей в карте в дестр-ре на 1 до тех пор пока от карты не "отвалятся" все указатели кроме одного. Когда же заводим карту через new и ставим на неё указатель счётчик указателей внутри карты ставим на 1 и потом увеличиваем на 1 каждый раз при копировании указателя на ту же карту в ещё одну переменную-указатель. При обнулении указателя уменьшаем соответственно. Для этого правда пришлось сделать классы DECK и SUBSET friend-ами по отношению к классу CARD. Хотя это нужно только на случай, если при вытаскивании карты из DECK в SUBSET (или из любого объекта некого класса в объект другого класса с указателями на карты) в колоде её оставляем т.е. не зануляем указатель и появляются множественные указатели на одну и ту же карту... Я на самом деле чего хотел понять и чего так до конца и не понял! Вот у нас есть, допустим массив УКАЗАТЕЛЕЙ на карты (а не самих карт!) т.е. массив адресов в памяти, например CARD* DECK[52] или CARD* SUBSET[size] с произвольным размером в данном случае не важно. Как НАДО подобный массив грамотно удалять в дест-ре: а.
б.
Вот что я хотел спросить.
0
|
![]() 90 / 60 / 32
Регистрация: 06.08.2020
Сообщений: 220
|
|
25.03.2022, 15:48 | 13 |
Ну идея вроде ясна, но в вашем коде в drawCrd создается временный указатель в который копируется адрес где лежит карта. Потом этот адрес зануляется и в итоге возвращается указатель на зануленный адрес.
0
|
0 / 0 / 0
Регистрация: 25.07.2020
Сообщений: 36
|
|
25.03.2022, 16:40 [ТС] | 14 |
Нет, почему? Сначала адрес сохраняется в локальной переменной функции, т.е. мы чётко запомнили по какому адресу лежит объект карты, этот адрес мы и вернём через возврат по значению локальной переменной!
Только потом "исходник" этого адреса массиве deck[] зануляется, так что ничего не пропадает. Мы же возвращаем сохранённый адрес определённого участка в памяти (то же самое что указатель на некоторую область). А второй указатель при этом перестаёт указывать на эту область т.е. адрес который в нём хранился становится nullptr. Всё прекрасно работает!
0
|
фрилансер
![]() 6327 / 5459 / 1108
Регистрация: 11.10.2019
Сообщений: 14,523
|
||||||
25.03.2022, 16:42 | 15 | |||||
realalexandro, PencilTorch, вот, слегка упростил и приукрасил. В логику не вникал
![]()
1
|
0 / 0 / 0
Регистрация: 25.07.2020
Сообщений: 36
|
||||||
25.03.2022, 16:49 [ТС] | 16 | |||||
Тогда получается надо так, если я правильно понял?:
Добавлено через 6 минут Алексей1153, Спасибо, будем учиться! Но я себе задачу поставил сделать без Vector-ов, просто с массивами и указателями т.е. по-колхозному))
0
|
фрилансер
![]() 6327 / 5459 / 1108
Регистрация: 11.10.2019
Сообщений: 14,523
|
|
25.03.2022, 16:58 | 17 |
0
|
25.03.2022, 16:58 | ||||||
Помогаю со студенческими работами здесь
17
Ошибка при компиляции двумерного динамического массива указателей Ошибка при создании динамического массива Ошибка при создании динамического массива Ошибка при создании динамического массива. Искать еще темы с ответами Или воспользуйтесь поиском по форуму:
|
|
![]() |
Новые блоги и статьи
![]() |
||||
Создание и использование компонентов в Vue 3
Reangularity 14.03.2025
Компонент в Vue - это автономный блок интерфейса, который содержит собственную разметку, логику и стили. Представьте себе кнопку, форму ввода или даже целую панель навигации - всё это можно оформить. . .
|
Vue 3: Создаем современное веб-приложение с Composition API
Reangularity 14.03.2025
В фронтенд-разработке Vue 3 выделяется своим прагматичным подходом. В отличие от React с его минималистичной философией "всё — JavaScript" или Angular с его всеобъемлющим корпоративным подходом, Vue. . .
|
Разработка контекстных меню в iOS
mobDevWorks 14.03.2025
С приходом iOS 13 Apple представила новый API для контекстных меню, который полностью заменил предыдущую технологию 3D Touch peek & pop. Хотя многие разработчики и пользователи испытывают ностальгию. . .
|
Лучшие практики оптимизации Docker Image
Mr. Docker 13.03.2025
Размер Docker-образа влияет на множество аспектов работы с контейнерами. Чем больше образ, тем дольше его загрузка в реестр и выгрузка из него. Для команд разработки, работающих с CI/ CD пайплайнами,. . .
|
Вопросы на собеседовании по Docker
Mr. Docker 13.03.2025
Ты сидишь напротив технического специалиста, и вдруг звучит вопрос про Docker Swarm или многоэтапные сборки. Пот на лбу? Не переживай, после этой статьи ты будешь готов ко всему! Эта статья будет. . .
|
Поиск текста в сносках : замена дефиса на тире или тире на дефис...
РоΜа 13.03.2025
Нужно было найти текст в сносках и заменить. Почему-то метод селекшн не сработал. . . пришлось гуглить. найденный на форумвба код пришлось править. Смысл - заменяет в сносках дефисы и тире на нужные. . . .
|
Real PATH definitions in bash scripts
jigi33 13.03.2025
Как поймать путь и путь к директории относительно запускаемого файла в BASH
1. поймать путь через вывод $(pwd)
2. более правильно - на основе realpath (см. скриншот)
|
Django или Flask: что выбрать для веб-разработки на Python
py-thonny 13.03.2025
Django – это высокоуровневый фреймворк, который придерживается философии "всё включено". Он предоставляет разработчику готовые решения для большинства типичных задач веб-разработки: от аутентификации. . .
|
Непрерывное развертывание в Java с Kubernetes
Javaican 13.03.2025
Чем так привлекателен Kubernetes для развертывания Java-приложений? Этот оркестратор контейнеров позволяет автоматизировать развертывание, масштабирование и управление контейнеризированными. . .
|
Предотвращение XSS, CSRF и SQL-инъекций в JavaScript
run.dev 13.03.2025
JavaScript занимает первые позиции среди языков веб-разработки, но его распространенность делает его привлекательной целью для злоумышленников. Межсайтовый скриптинг (XSS), межсайтовая подделка. . .
|