Josefina
Участник
Offline
|
|
« : 26-07-2011 10:05 » |
|
Ребята, доброго времени суток, я к вам за советом Честно говоря, не знала как бы поточнее назвать тему... Пытаюсь реализовать вот такую штуку (на самом деле проект не маленький, но с выделением-освобождением памяти не могу разобраться, поэтому сделала упрощенную модель): #include <stdio.h> #include <iostream.h>
//есть некоторый собственный класс
class Some { public: Some() { cout << "I am Some object " << endl; } ~Some() { cout << "I was delete " << endl ; }
void printSomeNumber() { cout << some_number; } void setSomeNumber(int temp) { some_number = temp; } private: int some_number; };
//в данной функции выделяется память для указателя на объект вышеописанного класса - //первый параметр, сколько будет выделено память сохраняется во второй параметр //далее в этот массив что-то сохранияется
void getObjectIdentificators(Some **oids, int &oid_count) { *oids = new Some[4]; oid_count = 5; for (int i = 0; i < 4; i++) { oids[i]->setSomeNumber(i); } }
// главная функция программы в которой инициализируется указатель // и удаляется, после того, как функция отработает
int main() {
Some *oids = NULL;
int oid_count;
getObjectIdentificators (&oids, oid_count);
for (int i = 0; i < oid_count; i++) { oids[i].printSomeNumber(); }
delete[] oids;
return 1; } Вопрос в чем, как правильно выделить память, чтобы она выделилась не для локального указателя и указатель все еще продолжал на нее указывать, после выхода из функции getObjectIdentificators? Добавлено через 2 минуты и 33 секунды:на данный момент, при выходе из функции getObjectIdentificators и попытке обратиться к oids происходит "Segmentation fault"
|
|
« Последнее редактирование: 26-07-2011 10:22 от Джон »
|
Записан
|
|
|
|
Джон
просто
Администратор
Offline
Пол:
|
|
« Ответ #1 : 26-07-2011 10:34 » |
|
Не совсем понял смысл oid_count, те ты хочешь сказать сколько элементов было добавлено? Но почему 5? Ты же создаешь только 4 элемента массива. Эт раз. Два. Вобще-то это очень нехороший стиль, выделять память в одном месте, а освобождать в другом. Тебе обязательно хочется передать возвращаемый параметр в ф-ю? Тогда может подойдёт такой метод. В ф-ю передаётся простой указатель. Если это NULL, то ф-я возвращает не void, а необходимый размер памяти. После этого создаётся нужный блок пмяти и указатель на него снова передаётся в ф-ю, которая уже теперь его заполняет. int getObjectIdentificators(Some *oids) { int oid_count = 5; if(oids) { for (int i = 0; i < oid_count; i++) { oids[i]->setSomeNumber(i); } } return oid_count; }
int main() { int oid_count = getObjectIdentificators (NULL); Some *oids = new Some[oid_count]; getObjectIdentificators (oids);
for (int i = 0; i < oid_count; i++) { oids[i].printSomeNumber(); }
delete[] oids;
return 1; }
|
|
|
Записан
|
Я вам что? Дурак? По выходным и праздникам на работе работать. По выходным и праздникам я работаю дома. "Just because the language allows you to do something does not mean that it’s the correct thing to do." Trey Nash "Physics is like sex: sure, it may give some practical results, but that's not why we do it." Richard P. Feynman "All science is either physics or stamp collecting." Ernest Rutherford "Wer will, findet Wege, wer nicht will, findet Gründe."
|
|
|
Вад
|
|
« Ответ #2 : 26-07-2011 10:42 » |
|
Если посмотреть внимательнее, где именно и при каких условиях происходит останов, выяснится, что Segmentation fault случается при i=4. Потому что элементов всего 4, а обращение идёт к 5-му: выход за границы блока
|
|
|
Записан
|
|
|
|
Josefina
Участник
Offline
|
|
« Ответ #3 : 26-07-2011 10:49 » |
|
Джон, тут дело в чем, задача сложнее.... Почему 5 написала - по ошибке видимо, согласна, что нелогично выглядит. Задача: Мне приходит "сообщение" (некоторый объект) от менеджера. Мне нужно с информацией из этого сообщения сделать множество манипуляций: часть 1: вычленить из этого сообщения индентификаторы, которую я как раз пытаюсь отделить в функцию 1) выделить сколько идентификаторов мне пришло и создать массив нужного размера для них 2) записать в этот массив идентификаторы 2.1) проверки на корректность идентификаторов 3) вернуть этот массив и его размер часть 2: тут идут следующие манипуляции с сообщением и вычлененным массивом .... часть 3: .... Добавлено через 8 минут и 53 секунды:Если посмотреть внимательнее, где именно и при каких условиях происходит останов, выяснится, что Segmentation fault случается при i=4. Потому что элементов всего 4, а обращение идёт к 5-му: выход за границы блока
да, тут я действительно не то написала, но исправление этого косячка не помогает, все тоже "Segmentation fault"
|
|
« Последнее редактирование: 26-07-2011 10:57 от Josefina »
|
Записан
|
|
|
|
Джон
просто
Администратор
Offline
Пол:
|
|
« Ответ #4 : 26-07-2011 11:39 » |
|
Ок, про 5 забыли. Мы говорим о несколько разных вещах. Я показал, как правильно работать с выделением памяти. Ты говоришь сейчас о логике программы. Одно никак не связано с другим. ЧТО делать, и КАК делать, согласись, не одно и то же. Давай так. Сначала мы определим, что мы делать можем, а что нет. Исключения (типа передачи указателя на указатель), в крайнем случае, всегда можно сделать. Но лучше без них. Никакой небходимости пока в этом нет. Из выше тобой сказанного, я не вижу проблем с реализацией выделения памяти. 1. Ты получаешь некий объект 2. Из этого объекта нужно получить инфу (сам объект менять не надо или?) 3. Что-то делать с полученой инфой. Если ошибся, поправь. Пока продолжу. Итак, у тебя должна быть ф-я-анализатор, те которая тебе сначала просто скажет, чего и сколько (злополучная 5) имеется в исходном объекте. Так? Скорей всего "чего" ты уже знаешь (твой Some), а вот сколько этого самого присутствует ф-я должна выяснить. В общем, логика довольно простая: //анализируем объект: info = AnalyzeObject(foreign_object);
//подготавливаем буфер для данных, на основе полученной инфы: dataBuffer = PrepareBuffer(info);
//получаем данные из объекта в буфер result = GetDataFromObject(foreign_object, dataBuffer);
//анализируем результат ...
//обрабатываем данные ... Думаю понятно. ps Да, к чему это я. Если ф-ции AnalyzeObject и GetDataFromObject одинаковые (например, нужно пробежать всю структуру, чтобы выяснить количество элементов), то их можно объединить в одну, которую вызывать два раза, как я и предложил в топике выше. Но лучше всё-таки кесарю - кесарево, а слесарю - слесарево. Если ф-я выполняет только то, что она должна делать, то отлаживать легче, и ошибки искать, да и гибкость в использовании тоже может пригодится. Скажу более, если структура исходного объекта сложная, то ф-ции лучше всего заменить объектами, каждый из которых выполняет свою, индивидуальную ф-ю.
|
|
« Последнее редактирование: 26-07-2011 11:46 от Джон »
|
Записан
|
Я вам что? Дурак? По выходным и праздникам на работе работать. По выходным и праздникам я работаю дома. "Just because the language allows you to do something does not mean that it’s the correct thing to do." Trey Nash "Physics is like sex: sure, it may give some practical results, but that's not why we do it." Richard P. Feynman "All science is either physics or stamp collecting." Ernest Rutherford "Wer will, findet Wege, wer nicht will, findet Gründe."
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #5 : 26-07-2011 11:42 » |
|
Вобще-то это очень нехороший стиль, выделять память в одном месте, а освобождать в другом. Тебе обязательно хочется передать возвращаемый параметр в ф-ю? Тогда может подойдёт такой метод. В ф-ю передаётся простой указатель. Если это NULL, то ф-я возвращает не void, а необходимый размер памяти. После этого создаётся нужный блок пмяти и указатель на него снова передаётся в ф-ю, которая уже теперь его заполняет. Это требует серьёзного уточнения. Во-первых, есть такое понятие, как "фабрика" - объект или функция, которая именно что создаёт нечто. Josefina, в данном случае Джон имеет в виду, что выделение памяти и инициализация - разные вещи. Если ты очищаешь память в main, то и выделяй память в main, однако ты можешь инициализировать данные в какой-то другой функции вроде getObjectIdentificators. Уж раз ты используешь классы, то разумно определить класс работы с идентификаторами - в его конструкторе память может выделяться, в деструкторе очищаться, и у него могут быть методы, обеспечивающие доступ к идентификаторам. Во-вторых, лично меня функции, требующие двойного вызова и возвращающие размер на NULL и что-то другое по смыслу - на указатель, мягко говоря бесят. Я это никогда не назову "хорошим стилем". И потому, что семантика такой функции "плавает", смыслы параметров меняются от вызова к вызову. И потому, что в рамках такого подхода далеко не всегда возможно обеспечить эффективность - нередко функция дважды должна выполнить одну и ту же работу. Josefina, отчего не бы не воспользоваться контейнерами стандартной библиотеки? Они сами управляют выделением памяти под хранение данных нужного размера. Даже если в дальнейшем по интерфейсам требуется передать указатель на область памяти с массивом идентификторов, всё равно можно использовать vector - он внутри содержит непрерывный участок памяти с данными.
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Джон
просто
Администратор
Offline
Пол:
|
|
« Ответ #6 : 26-07-2011 11:48 » |
|
Во-вторых, лично меня функции, требующие двойного вызова и возвращающие размер на NULL и что-то другое по смыслу - на указатель, мягко говоря бесят. Я это никогда не назову "хорошим стилем". И потому, что семантика такой функции "плавает", смыслы параметров меняются от вызова к вызову. И потому, что в рамках такого подхода далеко не всегда возможно обеспечить эффективность - нередко функция дважды должна выполнить одну и ту же работу. Димка, посмотри мой пример с псевдо кодом и ps. ps А аргмуенты голословны. Что значит семантика плавает? Смыслы параметров не меняются, с чего ты взял? Параметр - указатель, он может быть нулевым. Так что именно тебя бесит.
|
|
« Последнее редактирование: 26-07-2011 11:51 от Джон »
|
Записан
|
Я вам что? Дурак? По выходным и праздникам на работе работать. По выходным и праздникам я работаю дома. "Just because the language allows you to do something does not mean that it’s the correct thing to do." Trey Nash "Physics is like sex: sure, it may give some practical results, but that's not why we do it." Richard P. Feynman "All science is either physics or stamp collecting." Ernest Rutherford "Wer will, findet Wege, wer nicht will, findet Gründe."
|
|
|
Вад
|
|
« Ответ #7 : 26-07-2011 12:12 » |
|
да, тут я действительно не то написала, но исправление этого косячка не помогает, все тоже "Segmentation fault"
Тогда настоятельный совет подружиться с отладчиком. Потому что я вот так сразу тоже проглядел, а гадать, откуда segfault, дольше, чем запустить и посмотреть. Ну так вот: void getObjectIdentificators(Some **oids, int &oid_count) { *oids = new Some[4]; oid_count = 5; for (int i = 0; i < 4; i++) { oids[i]->setSomeNumber(i); // <- } }
В 7 строчке куда обращаемся? Что будет в oids[i]?
|
|
|
Записан
|
|
|
|
Джон
просто
Администратор
Offline
Пол:
|
|
« Ответ #8 : 26-07-2011 12:34 » |
|
Вад, ага, точно. У меня примерно как у Димки, такую личную неприязнь к указателям на указатели испытываю, даже кушать не могу.
|
|
|
Записан
|
Я вам что? Дурак? По выходным и праздникам на работе работать. По выходным и праздникам я работаю дома. "Just because the language allows you to do something does not mean that it’s the correct thing to do." Trey Nash "Physics is like sex: sure, it may give some practical results, but that's not why we do it." Richard P. Feynman "All science is either physics or stamp collecting." Ernest Rutherford "Wer will, findet Wege, wer nicht will, findet Gründe."
|
|
|
Finch
Спокойный
Администратор
Offline
Пол:
Пролетал мимо
|
|
« Ответ #9 : 26-07-2011 13:28 » |
|
А почему нельзя сделать проше. Например вот так: Some* getObjectIdentificators(int &oid_count) { oid_count = 4; Some *oids = new Some[oid_count]; for (int i = 0; i < oid_count; i++) { oids[i].setSomeNumber(i); } return oids; } Тогда вызов будет таким: Some *oids = NULL;
int oid_count;
oids = getObjectIdentificators (oid_count);
|
|
|
Записан
|
Не будите спашяго дракона. Джаффар (Коша)
|
|
|
Джон
просто
Администратор
Offline
Пол:
|
|
« Ответ #10 : 26-07-2011 14:00 » |
|
Да сделать-то можно. Вопрос - как правильно? ps Лично мне такой вариант не нравится. Хотя бы потому, что не знаешь как освобождать память: delete или delete [].
|
|
« Последнее редактирование: 26-07-2011 14:01 от Джон »
|
Записан
|
Я вам что? Дурак? По выходным и праздникам на работе работать. По выходным и праздникам я работаю дома. "Just because the language allows you to do something does not mean that it’s the correct thing to do." Trey Nash "Physics is like sex: sure, it may give some practical results, but that's not why we do it." Richard P. Feynman "All science is either physics or stamp collecting." Ernest Rutherford "Wer will, findet Wege, wer nicht will, findet Gründe."
|
|
|
Finch
Спокойный
Администратор
Offline
Пол:
Пролетал мимо
|
|
« Ответ #11 : 26-07-2011 14:22 » |
|
Ну я бы делал так #include <iostream> #include <vector>
class Some { public: Some() { std::cout << "I am Some object " << std::endl; } Some(const Some& orig) { some_number = orig.some_number; std::cout << "I am Some object (copy)" << std::endl; } ~Some() { std::cout << "I was deleted " << std::endl ; }
void printSomeNumber() { std::cout << some_number << std::endl; } void setSomeNumber(int temp) { some_number = temp; } private: int some_number; };
typedef std::vector<Some> ListSome; typedef ListSome::iterator ListSomeItr;
void getObjectIdentificators(ListSome &orig) { Some some; #define OID 4 for(int i=0; i < OID; i++) { some.setSomeNumber(i); orig.push_back(some); } }
int main() { ListSome list; getObjectIdentificators (list); for (ListSomeItr i = list.begin(); i != list.end(); ++i) { (*i).printSomeNumber(); } return 0; }
|
|
|
Записан
|
Не будите спашяго дракона. Джаффар (Коша)
|
|
|
Антон (LogRus)
|
|
« Ответ #12 : 27-07-2011 03:34 » |
|
а еще лучше вот так #include <iostream> #include <vector>
class Some { public: Some() { std::cout << "I am Some object " << std::endl; } Some(const Some& orig) { some_number = orig.some_number; std::cout << "I am Some object (copy)" << std::endl; } ~Some() { std::cout << "I was deleted " << std::endl ; }
void printSomeNumber() { std::cout << some_number << std::endl; } void setSomeNumber(int temp) { some_number = temp; } private: int some_number; };
typedef std::vector<Some> ListSome; typedef ListSome::iterator ListSomeItr; const size_t OIDS_COUNT=4; void getObjectIdentificators(ListSome &orig) { Some some; for(int i=0; i < OIDS_COUNT; i++) { orig.resize(orig.size()+1); orig.back().setSomeNumber(i); } }
int main() { ListSome list; list.reserve(OIDS_COUNT);
getObjectIdentificators (list); for (ListSomeItr i = list.begin(); i != list.end(); ++i) { i->printSomeNumber(); } return 0; }
|
|
|
Записан
|
Странно всё это....
|
|
|
Finch
Спокойный
Администратор
Offline
Пол:
Пролетал мимо
|
|
« Ответ #13 : 27-07-2011 04:23 » |
|
Антон (LogRus), по условиям задачи OIDS_COUNT это что то эфимерное и цифра взята с потолка. 1) выделить сколько идентификаторов мне пришло и создать массив нужного размера для них
Так что константы тут ну никак не катят. Поэтому define я и завел внутрь функции. И обошелся без резервации. А чем лучше orig.resize(orig.size()+1); orig.back().setSomeNumber(i); Чем это some.setSomeNumber(i); orig.push_back(some); Без учета и с учетом вышесказанного?
|
|
|
Записан
|
Не будите спашяго дракона. Джаффар (Коша)
|
|
|
Josefina
Участник
Offline
|
|
« Ответ #14 : 27-07-2011 04:35 » |
|
Ребята, ого, спасибо за ответы Буду отвечать по порядку: Josefina, в данном случае Джон имеет в виду, что выделение памяти и инициализация - разные вещи. Если ты очищаешь память в main, то и выделяй память в main, однако ты можешь инициализировать данные в какой-то другой функции вроде getObjectIdentificators.
я почему-то думала, что основной бонус использования new/delete - это то, что можно выделить и освободить память в любом месте программы Уж раз ты используешь классы, то разумно определить класс работы с идентификаторами - в его конструкторе память может выделяться, в деструкторе очищаться, и у него могут быть методы, обеспечивающие доступ к идентификаторам. Вообще класс, реализующий конкретный идентификатор есть (в сторонней бибилиотеке). Реализация класса, работающего с набором идентификаторов не вариант, так как функции этой сторонней библиотеки принимают на вход массив и только массив.... Josefina, отчего не бы не воспользоваться контейнерами стандартной библиотеки? Они сами управляют выделением памяти под хранение данных нужного размера. Даже если в дальнейшем по интерфейсам требуется передать указатель на область памяти с массивом идентификторов, всё равно можно использовать vector - он внутри содержит непрерывный участок памяти с данными.
Как раз таки потому, что мне нужно соблюсти входные интерфейсы сторонней библиотеки Добавлено через 14 минут и 29 секунд:Вад, по идее в oids[ i ] должен сохраниться индекс i. про подружиться с отладчиком понято и принято ))) Finch, как бы почему я не могу использовать контейнеры я объяснила, интерфейсы библиотеки требуют на вход массив. У меня вопрос по вашему примеру, зачем явно переопределять конструктор копирования? Finch, Антон (LogRus), спасибо за примеры кода
|
|
« Последнее редактирование: 27-07-2011 04:49 от Josefina »
|
Записан
|
|
|
|
Finch
Спокойный
Администратор
Offline
Пол:
Пролетал мимо
|
|
« Ответ #15 : 27-07-2011 04:55 » |
|
Josefina, Потому что vector и другие контейнеры STL используют его при помешении элемента. Если у тебя не динамически созданные переменные внутри класса, то можно обойтись конструтором копирования по умолчанию. Иначе он просто обязателен. Лучше его сразу написать, чтоб потом не наделать ошибок. Когда появятся динамические переменные.
|
|
|
Записан
|
Не будите спашяго дракона. Джаффар (Коша)
|
|
|
Антон (LogRus)
|
|
« Ответ #16 : 27-07-2011 04:59 » |
|
А чем лучше orig.resize(orig.size()+1); orig.back().setSomeNumber(i); Чем это some.setSomeNumber(i); orig.push_back(some); Без учета и с учетом вышесказанного? в таком коде на одно копирование меньше, а если ты оперируешь большими и сложными объектами, то этот нехитрый трюк даёт большой прирост производительности Как раз таки потому, что мне нужно соблюсти входные интерфейсы сторонней библиотеки Вообще то это не аргумент, если честно особенно если ты знаешь о том, что такое void foo(MyClass * array); ...... std::vector<MyClass> a; ...... foo(&a[0]); вполне допустимо, т.к. вектор хранит элементы в неприрывном блоке памяти и использует new (по умолчанию) для выделения памяти под элементы
|
|
|
Записан
|
Странно всё это....
|
|
|
Вад
|
|
« Ответ #17 : 27-07-2011 06:15 » |
|
Вад, по идее в oids[ i ] должен сохраниться индекс i. про подружиться с отладчиком понято и принято )))
Хорошо, ещё раз. oids имеет тип Some**, оно указывает на "указатель на Some" (на самом деле, указывает на динамический массив Some, что то же самое). Так что такое oids[i], и почему это работает не так, как задумано? В качестве подсказки, если вдруг это поможет, oids[i] эквивалентно *(oids + i).
|
|
« Последнее редактирование: 27-07-2011 06:17 от Вад »
|
Записан
|
|
|
|
Josefina
Участник
Offline
|
|
« Ответ #18 : 27-07-2011 08:51 » |
|
ура, с контейнерами действительно все красиво получается ) доделаю и покажу, что получилось ну и на вопрос попробую ответить
|
|
|
Записан
|
|
|
|
Dimka
Деятель
Команда клуба
Offline
Пол:
|
|
« Ответ #19 : 27-07-2011 09:19 » |
|
я почему-то думала, что основной бонус использования new/delete - это то, что можно выделить и освободить память в любом месте программы Несомненно. Этим и отличается куча от стека. Но если память выделяется, её непременно нужно освобождать. Совет делать это в одном месте облегчает программисту работу с кодом - программисту точно известно, где, при каких условиях, сколько памяти выделяется и освобождается. Одним местом может быть функция, класс, модуль (файл cpp). Это не из области требований языка, это из области "хорошего стиля" кодирования. Если язык не использует автоматическую сборку мусора, то гарантию освобождения выделенной памяти должен давать программист, а обсуждаемый подход является одной из стратегий обеспечения такой гарантии. Другой стратегией является, например, использование "умных указателей" (auto_ptr в C++).
|
|
|
Записан
|
Программировать - значит понимать (К. Нюгард) Невывернутое лучше, чем вправленное (М. Аврелий) Многие готовы скорее умереть, чем подумать (Б. Рассел)
|
|
|
Джон
просто
Администратор
Offline
Пол:
|
|
« Ответ #20 : 27-07-2011 11:33 » |
|
А давайте уговорим Josefina передалать всё на C# и добавить Extension к этому объекту. Будет ещё красивей.
|
|
|
Записан
|
Я вам что? Дурак? По выходным и праздникам на работе работать. По выходным и праздникам я работаю дома. "Just because the language allows you to do something does not mean that it’s the correct thing to do." Trey Nash "Physics is like sex: sure, it may give some practical results, but that's not why we do it." Richard P. Feynman "All science is either physics or stamp collecting." Ernest Rutherford "Wer will, findet Wege, wer nicht will, findet Gründe."
|
|
|
Dmitry
Помогающий
Offline
|
|
« Ответ #21 : 27-07-2011 15:03 » |
|
Элементы в контейнере же в куче хранятся? Такой момент - при добавлении нового элемента с помощью push_back мы его передаём по значению, т.е. вызывается конструктор копирования и в стеке создаётся новый объект-копия. И этот новый объект локальный для ф-ии push_back, при выходе из ф-ии для него должен вызываеться деструктор. Собственно вопрос - почему этого не происходит (объект не уничтожается, если бы уничтожался, в контейнер бы ничего не добавлялось в итоге)? ps C++ плохо помню в таком коде на одно копирование меньше, а если ты оперируешь большими и сложными объектами, то этот нехитрый трюк даёт большой прирост производительности
Без резервирования, четыре эл-та добавляем: I am Some object (copy) ---------- I am Some object (copy) I am Some object (copy) I was deleted ---------- I am Some object (copy) I am Some object (copy) I am Some object (copy) I was deleted I was deleted ---------- I am Some object (copy) I am Some object (copy) I am Some object (copy) I am Some object (copy) I was deleted I was deleted I was deleted ----------
|
|
|
Записан
|
|
|
|
Вад
|
|
« Ответ #22 : 27-07-2011 15:05 » |
|
Dmitry, если я правильно понял вопрос, то, наверное, потому что в push_back передача по ссылке -- никаких других копий не создаётся, кроме той, что помещается в контейнер. А 'I was deleted' в приведённом логе - это, надо думать, следы переаллокации общего динамического массива.
|
|
|
Записан
|
|
|
|
Dmitry
Помогающий
Offline
|
|
« Ответ #23 : 27-07-2011 15:43 » |
|
Вад, Точно, по ссылке! Спасибо, а то я чуть голову себе не сломал. По поводу переаллокации - мне показалось странным, что ёмкость контейнера автоматически не увеличивается на порядок. Всё ручками надо делать
|
|
|
Записан
|
|
|
|
Вад
|
|
« Ответ #24 : 27-07-2011 17:57 » |
|
По поводу переаллокации - мне показалось странным, что ёмкость контейнера автоматически не увеличивается на порядок. Всё ручками надо делать Надо исходники смотреть - стратегия переаллокации зависит от реализации. Вообще, и правда странно.
|
|
|
Записан
|
|
|
|
|