61 / 5 / 1
Регистрация: 03.06.2013
Сообщений: 294
Записей в блоге: 2
1

Нужно ли очищать char * old value как?

29.04.2019, 23:01. Показов 2452. Ответов 55
Метки нет (Все метки)

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <string>
#include <stdio.h>
 
 
int main()
{
     long adr=1;
 char * str1, *str2;
 
 
str1 = "Hello1";
printf ("&str1 = %d ",&str1); 
adr = (long)str1;
 
printf ("&hello1 = %d ",adr); 
 
str2 = (char*)adr;
//free (str1);
 
str1 = "new string";
printf ("\n");
printf ("str1 = %s ",str1);
printf ("str2 = %s ",str2);
}
изучая стринги, решил проверить что происходит со старым значением указателя. незнаю правильно ли проверил, но вроде старое значение Hello1 получается так и висит в памяти. как его удалить ?
__________________
Помощь в написании контрольных, курсовых и дипломных работ, диссертаций здесь
0
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
29.04.2019, 23:01
Ответы с готовыми решениями:

Нужно ли очищать термопласту,если да то как?
Вообщем после сборке ПК,походу заляпал разъём видюхи и материнки термопластой.Картинки есть.Видео...

Нужно ли перед удалением контейнера очищать его?
Нужно ли перед удалением контейнера очищать его или в деструкторе по умолчанию вызывается метод...

Класс list, когда нужно очищать память?
не могу разобраться когда нужно очищать память &quot;delete input_&quot; и почему каждый раз выделяя помять...

Нужно ли очищать память, выделенную под вектор?
Доброго времени суток. При работе с динамическими массивами всегда чистил память оператором delete....

55
С чаем беда...
Эксперт CЭксперт С++
9989 / 5341 / 1461
Регистрация: 18.10.2014
Сообщений: 12,854
03.05.2019, 03:46 41
Цитата Сообщение от TRam_ Посмотреть сообщение
alexbmd, строки char * в С и С++ отличаются тем, что конец строки отмечается символом '\0', который соответствует значению 0 (или NULL, если речь об одном из макросов в С).
Никакой связи между макросом NULL и символом конца строк \0 нет, и даже упоминать NULL в этом контексте - неправильно.
0
61 / 5 / 1
Регистрация: 03.06.2013
Сообщений: 294
Записей в блоге: 2
03.05.2019, 20:14  [ТС] 42
Цитата Сообщение от alexbmd Посмотреть сообщение
вопрос куда стучиться *pa[i] в случае простого двумерного массива ?
коменты я еще не читал, я их обезательно прочту вскоре, просто я на два дня безвылазно нырнул в массивы-указатели, чтоб чтото понять, и кажеться я понял ответ на свой вопрос.
сейчас я попытаюсь объяснить , поправьте меня плиз если я неправ

в первом случае
C++
1
2
const char *p[] = {"dog", "cat", "mouse", NULL};
printf("*p = %s \n",p[3]);
у нас массив указателей и последней указатель имеет нолевой адрес и соответсвенно нолевое значение . и while (p[i]) когда доходит до адрес нуль while(0) выходит тк. поймали ноль как адрес

во втором случае
C++
1
2
char p[][6] = {"dog", "cat", "mouse", NULL};
printf("*p = %s \n",*p[3]);
у нас массив значений и третья строка имеет значение ноль . но адрес у третьей строки есть, поэтому while (p[i]) не фейлиться, и только если взять значение по этому адресу while(*p[i]) то мы поймаем ноль как значение

непонятно другое. выше описаное повидение валидно только для нуля. если брать вторую ячейку то и в первом и вов тором варианте мы прекрасно печатаем mouse в консоль по p[2] и разименовывать указатель на не нужно
0
С чаем беда...
Эксперт CЭксперт С++
9989 / 5341 / 1461
Регистрация: 18.10.2014
Сообщений: 12,854
03.05.2019, 21:38 43
Цитата Сообщение от alexbmd Посмотреть сообщение
непонятно другое. выше описаное повидение валидно только для нуля. если брать вторую ячейку то и в первом и вов тором варианте мы прекрасно печатаем mouse в консоль по p[2] и разименовывать указатель на не нужно
Никакого "разыменования" в этом случае не допускается ни в первом, ни во втором случае.

Вот это

C++
1
2
char p[][6] = {"dog", "cat", "mouse", NULL};
printf("*p = %s \n",*p[3]);
- это грубая ошибка в printf. Спецификатор %s требует аргумента-указателя, а вы передаете туда char (который превращается в int).

И еще раз

C++
1
char p[][6] = {"dog", "cat", "mouse", NULL};
- так нельзя. И компилироваться это может только по счастливому стечению обстоятельств.
0
61 / 5 / 1
Регистрация: 03.06.2013
Сообщений: 294
Записей в блоге: 2
03.05.2019, 22:50  [ТС] 44
Я так печатать не собираюсь, это чисто для того чтоб увидить что там while видит. Только так видит(null) и только так он срабатывает.

А почему так нельзя это же не const литералы строк ? А простой дву мерный массив доступный для записи?

Еще раз. Через p[2] mouse печатает для обоих вариантов. Но вот как условие для while p [i] не отрабатывает для двумерного массива.
0
С чаем беда...
Эксперт CЭксперт С++
9989 / 5341 / 1461
Регистрация: 18.10.2014
Сообщений: 12,854
04.05.2019, 01:07 45
Цитата Сообщение от alexbmd Посмотреть сообщение
А почему так нельзя это же не const литералы строк ?
Я подробно ответил на этот вопрос в сообщении #39.

Макрос NULL предназначен для использования только в указательных контекстах: для инициализации/присваивания указателей или для сравнения на равенство с указателями. Все. Больше NULL нигде применять нельзя.
0
зомбяк
1562 / 1211 / 344
Регистрация: 14.05.2017
Сообщений: 3,925
04.05.2019, 04:05 46
TheCalligrapher, и в настоящее время для указателей используется nullptr, а NULL только для старых компиляторов, не поддерживающих С++11

Добавлено через 16 минут
alexbmd, вместо while (*pa[i])сделай

C++
1
2
3
4
char p[][6] = {"dog", "cat", "mouse", "other"};
const int p_count = sizeof(p)/(6*sizeof(char));
for(int i = 0; i < p_count; i++)
   printf("p[%d] = %s \n",i, p[i]);
Чем выдумывать нечто, что где-то как-то скомпилируется, а где-то вывалит ошибку компиляции или вылет программы.
Кроме того, задавать размер массива с помощью числа считается дурным тоном (т.к. это число, как в коде выше, может потребоваться во многих местах, а через "правка - заменить всё" даже в небольшом проекте это делать будет неудобно). Задавай размер массива с помощью макроса или константной переменной. Например так, просто и ясно:
C++
1
2
3
const int p_str_len = 5+1;
const int p_count = 4;  
char p[p_count][p_str_len] = {"dog", "cat", "mouse", "other"};
И да, почему 5+1 - потому что поместится только 5 или менее символов и тот самый обязательный для строк ноль (символ конца строки)

Добавлено через 10 минут
Цитата Сообщение от alexbmd Посмотреть сообщение
А почему так нельзя это же не const литералы строк ?
Если нужен массив указателей на строки в массивах, можешь сделать например так:

C++
1
2
3
4
5
6
7
8
const int str_len = 3+1;
char dog_str[str_len] = "dog";
char cat_str[str_len] = "cat";
 
char *p[] = {dog_str, cat_str, nullptr};
char **p_copy = p;
while(*p_copy)
   printf("p = %s \n",*(p_copy++));
0
С чаем беда...
Эксперт CЭксперт С++
9989 / 5341 / 1461
Регистрация: 18.10.2014
Сообщений: 12,854
04.05.2019, 05:28 47
Цитата Сообщение от TRam_ Посмотреть сообщение
TheCalligrapher, и в настоящее время для указателей используется nullptr, а NULL только для старых компиляторов, не поддерживающих С++11
Для старых или не для старых - это дело десятое.

Я веду речь именно о том, что NULL теоретически может быть определен как nullptr и не будет работать ни в каком контексте, кроме указательных. Соответственно код автора вопроса может не компилироваться вообще.

Цитата Сообщение от TRam_ Посмотреть сообщение
C++
1
2
3
4
char p[][6] = {"dog", "cat", "mouse", "other"};
const int p_count = sizeof(p)/(6*sizeof(char));
for(int i = 0; i < p_count; i++)
   printf("p[%d] = %s \n",i, p[i]);
Не ясно, зачем понадобилось это извращение с 6*sizeof(char), когда классическая универсальная идиома

C++
1
const int p_count = sizeof p / sizeof *p;
здесь сработала бы прекрасно.

Цитата Сообщение от TRam_ Посмотреть сообщение
Кроме того, задавать размер массива с помощью числа считается дурным тоном (т.к. это число, как в коде выше, может потребоваться во многих местах, а через "правка - заменить всё" даже в небольшом проекте это делать будет неудобно). Задавай размер массива с помощью макроса или константной переменной. Например так, просто и ясно:
Это неверно. Никто вас не заставляет повторять это число где-либо снова. Пока это массив, вы можете получить размер массива либо через std::size, либо через вышеприведенный прием с sizeof p / sizeof *p.

Это всегда было популярной альтернативной техникой, заменяющей заведение именованной константы. В большинстве случаев именованная константа мне нравится больше, но все равно не надо навязывать такие огульные заявления, как "задавать размер массива с помощью числа считается дурным тоном". Нет, не считается, если вы далее не повторяете эту константу.

Более того, в ситуации, когда размер массива определяется в первую очередь количеством инициализаторов, именно предзадавать размер массива константой - дурной тон.

Вот это - плохо:

Цитата Сообщение от TRam_ Посмотреть сообщение
C++
1
2
3
const int p_str_len = 5+1;
const int p_count = 4;  
char p[p_count][p_str_len] = {"dog", "cat", "mouse", "other"};
Намного лучше: оставить в объявлении массива [] вместо [p_count], а только после этого сделать

C++
1
const int p_count = sizeof p / sizeof *p;
Цитата Сообщение от TRam_ Посмотреть сообщение
Если нужен массив указателей на строки в массивах, можешь сделать например так:

C++
1
2
3
4
5
const int str_len = 3+1;
char dog_str[str_len] = "dog";
char cat_str[str_len] = "cat";
 
char *p[] = {dog_str, cat_str, nullptr};
Это имеет смысл только в том случае, если кому-то нужен массив указателей на модифицируемые строки, да еще и с конкретным одинаковым размером буфера. Откуда вдруг взялась такая задача и почему вдруг о ней зашла речь - мне не ясно.
1
61 / 5 / 1
Регистрация: 03.06.2013
Сообщений: 294
Записей в блоге: 2
06.05.2019, 01:49  [ТС] 48
Цитата Сообщение от zayats80888 Посмотреть сообщение
это не корректное выражение, оно эквивалентно pa[0][i]
тогда мы распечатаем символы только первого элемента всеже не эквивалентно
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
Никакого "разыменования" в этом случае не допускается ни в первом, ни во втором случае.
да чтобы получить адрес всего элемента (он же совпадает с адресом 0го символа этого элемента) нам разыменовывать не надо. чудесным образом и вся строка нам возращается без разименовывания. НО. опять перечитал тону литературы и хотел бы вернуться к этой загадачной конструкции. ни одна из книжек не даёт ответа на 2 вопроса не поддающихся ни какой логике. может кто из местных гуру сможет объяснить
C++
1
2
3
char pa[][10] = {"go", "cat", "mouse", "с"};
int i = 0;
while (*pa[i]) printf("*pa[%d] = %s\n", i-1 , pa[i++]);
NULL нам не нужен как и нуль терминатор. всё и так работает но

вопрос 1)
a) чем большев элементов, тем больше длину надо делать. вот для 4 элементов надо делать длину в 10 при том что все элементы могут быть 2 символа + 1 терминатор, всеравно надо делать 10 иначе на принт выводиться одна или более (в зависимости от комбинации) лишних строк с мусором.
b) если добавить строку терминатора {"go", "cat", "mouse", "c", '\0'} то длину можно указать как и ожидается в 6 и будет работать правильно. пустую строку тоже не выводит. как и задумано. и тут всё понятно.
почему же такое поведение как в пункте а ?

вопрос 2)
pa[i] имеет адрес начала каждого элемента, т.е. именно pa[i] (или *(pa+i) если кому то так привычнее) и только так способно вывести на принт весь элемент (или массив символов если кому так привычнее). В то время как *pa[i] выводит нулевой символ каждого элемента. Т.е. pa[i] адресс нулевого символа , *pa[i] сам символ. и вот while срабатывает только по символу. куда ведут адреса, что делает разименовывание это всё понятно. но вы вот говорите
Никакого "разыменования" в этом случае не допускается
однако while не отрабатывает по pa[i]. почему?
Далее, если у нас такой массив как выше то pa[4] ведет на какойто адрес в памяти (мусор), и while (по адресу) не фейлится. тут с арифметикой все понятно. Но вот если у нас такой массив char *pa[] = {"go", "cat", "mouse", "c"}; то pa[4] ни куда не ведет (null) и соответсвено while нормально срабатывает по адресу. ПОЧЕМУ? ведь точно такаяже арифметика. неужели все дело в read-only memory?
0
С чаем беда...
Эксперт CЭксперт С++
9989 / 5341 / 1461
Регистрация: 18.10.2014
Сообщений: 12,854
06.05.2019, 02:09 49
Цитата Сообщение от alexbmd Посмотреть сообщение
C++
1
2
3
char pa[][10] = {"go", "cat", "mouse", "с"};
int i = 0;
while (*pa[i]) printf("*pa[%d] = %s\n", i-1 , pa[i++]);
NULL нам не нужен как и нуль терминатор. всё и так работает но
Это не верно. Поведение этого кода не определено. Этот код "работает" на чистом случайном везении.

Если вы хотите использовать цикл while (*pa[i]), то тогда обязательно потребуется дополнительный элемент массива с нулем в первом символе

[CPP]
char pa[][10] = {"go", "cat", "mouse", "с", ""};
char pa[][10] = {"go", "cat", "mouse", "с", 0};
char pa[5][10] = {"go", "cat", "mouse", "с"};
// и т.п.
[CPP]

Цитата Сообщение от alexbmd Посмотреть сообщение
вопрос 1)
a) чем большев элементов, тем больше длину надо делать. вот для 4 элементов надо делать длину в 10 при том что все элементы могут быть 2 символа + 1 терминатор, всеравно надо делать 10 иначе на принт выводиться одна или более (в зависимости от комбинации) лишних строк с мусором.
Это и есть то самое неопределенное поведение, о котором я говорю. Поменяли размер строки - ваше "случайное везение" закончилось и все развалилось.

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

Цитата Сообщение от alexbmd Посмотреть сообщение
b) если добавить строку терминатора {"go", "cat", "mouse", "c", '\0'} то длину можно указать как и ожидается в 6 и будет работать правильно. пустую строку тоже не выводит. как и задумано. и тут всё понятно.
почему же такое поведение как в пункте а ?
Как "почему"? У вас в памяти за массивом располагается "мусор". Вот вы и вылетаете туда и печатаете этот мусор.

Цитата Сообщение от alexbmd Посмотреть сообщение
вопрос 2)
pa[i] имеет адрес начала каждого элемента, т.е. именно pa[i] (или *(pa+i) если кому то так привычнее) и только так способно вывести на принт весь элемент (или массив символов если кому так привычнее). В то время как *pa[i] выводит нулевой символ каждого элемента. Т.е. pa[i] адресс нулевого символа , *pa[i] сам символ. и вот while срабатывает только по символу. куда ведут адреса, что делает разименовывание это всё понятно. но вы вот говорите
однако while не отрабатывает по pa[i]. почему?
Ничего не понятно.

В любом случае, pa[i] - это никакой не "адрес", а целый массив. Массив неявно приводится к указателю (адресу).
https://www.cyberforum.ru/cpp-beginners/thread1941443.html#post10231882

Цитата Сообщение от alexbmd Посмотреть сообщение
Но вот если у нас такой массив char *pa[] = {"go", "cat", "mouse", "c"}; то pa[4] ни куда не ведет (null)
и соответсвено while нормально срабатывает по адресу. ПОЧЕМУ? ведь точно такаяже арифметика. неужели все дело в read-only memory?
Ничего не понятно.

Во-первых, pa[4] здесь никакой не (null), а мусор. В-вторых, этот массив состоит из указателей, а массив выше - из массивов, а не из указателей. "Арифметика" тут совершенно разная.
0
61 / 5 / 1
Регистрация: 03.06.2013
Сообщений: 294
Записей в блоге: 2
08.05.2019, 15:01  [ТС] 50
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
В любом случае, pa[i] - это никакой не "адрес"
p[i] - это всётаки адрес но адресс всего массива(всей строчки которая я/я одномерным массивом)
C++
1
2
3
int arr1[] = {1,5,9};
int (*ptr)[3] = &arr1;
ptr++; //где у нас теперь указатель? нет не arr1[1]
правильно?

//далее было много букф но оказалось что *p[i] == *(*(p+i)) и вопросы отпали я по началу думал достаточно одного разименовывания.
////на другом компиляторе оказалось что ноль нужен и в конце *pa[] и в конце pa[][] я по началу думал все компиляторы одинаковы полезны
0
зомбяк
1562 / 1211 / 344
Регистрация: 14.05.2017
Сообщений: 3,925
08.05.2019, 15:25 51
Цитата Сообщение от alexbmd Посмотреть сообщение
while (*pa[i])
эквивалентно
C++
1
while (pa[i][0])
Если i оказывается вне пределов массива, то это будет "чтение вне выделенной памяти". А это приводит к вылету, если не на 100 попытках, а например на 1000. Т.е. до того момента как операционная система начнёт замечать, что приложение читает данные не там, где можно.

Добавлено через 2 минуты
А то что при отладке оно иногда срабатывает правильно - ну значит среди мусора в памяти оказалось достаточное число нулей, чтобы оказаться и в pa[4][0]

Добавлено через 8 минут
Цитата Сообщение от alexbmd Посмотреть сообщение
int arr1[] = {1,5,9};
int (*ptr)[3] = &arr1;
Мухи - отдельно, котлеты - отдельно. Если делаешь массив указателей, то тогда и задаваться они должны просто указателями, т.е.

C++
1
2
    int arr1[] = {1,5,9};
    int *ptr[3] = {arr1, arr1+1, arr1+2};
И обращаться к ним нужно будет как

C++
1
*ptr[1]
А указатель на массив, как в твоём случае, можно только разыменовывать, т.к. память до твоего массива arr1[] и за ним не выделена под массивы чисел. То есть можно только и исключительно *ptr и соответственно (*ptr)[1] и т.д.

Добавлено через 4 минуты
Цитата Сообщение от alexbmd Посмотреть сообщение
я по началу думал все компиляторы одинаковы полезны
Одинакового мусора в памяти не бывает. Он часто похож, он почти никогда не одинаков. А вот какие-то его места - да, могут повторяться. Компилятор тут не при чём.
0
61 / 5 / 1
Регистрация: 03.06.2013
Сообщений: 294
Записей в блоге: 2
08.05.2019, 16:39  [ТС] 52
Цитата Сообщение от TRam_ Посмотреть сообщение
Если делаешь массив указателей
нет я это показал что есть и аддресс всего массива а в остальном согласен с вами.
0
зомбяк
1562 / 1211 / 344
Регистрация: 14.05.2017
Сообщений: 3,925
08.05.2019, 16:47 53
Цитата Сообщение от alexbmd Посмотреть сообщение
ptr++; //где у нас теперь указатель? нет не arr1[1]
Для arr1[1] нужно
C++
1
*((*ptr) + 1)
А оператор инкремента применять для результата разыменования указателя на массив нельзя, т.к. сам массив не является модифицируемой переменной.
0
С чаем беда...
Эксперт CЭксперт С++
9989 / 5341 / 1461
Регистрация: 18.10.2014
Сообщений: 12,854
08.05.2019, 17:22 54
Цитата Сообщение от alexbmd Посмотреть сообщение
p[i] - это всётаки адрес но адресс всего массива(всей строчки которая я/я одномерным массивом)
Нет, для случая, когда p объявлено как двумерный массив, это не верно.

p[i] - это сам массив, а не "адрес массива". p[i] имеет свойство преобразовываться в указатель (адрес), но это будет не "адрес всего массива", а адрес его нулевого элемента, т.е. &p[i][0].

Цитата Сообщение от alexbmd Посмотреть сообщение
C++
1
2
3
int arr1[] = {1,5,9};
int (*ptr)[3] = &arr1;
ptr++; //где у нас теперь указатель? нет не arr1[1]
правильно?
Замечательно, но не ясно, как этот пример связан с изначальным вопросом про p[i]. Нет, то, что происходит в этом примере к исходному p[i] отношения не имеет.
0
61 / 5 / 1
Регистрация: 03.06.2013
Сообщений: 294
Записей в блоге: 2
09.05.2019, 15:25  [ТС] 55
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
Замечательно, но не ясно, как этот пример связан
а точно так же связан. ptr++ нас перекидывает за массив. т.к. в этом примере он один то за ним мусор было бы их три. как в случае изначального разговора про pa[i] то ptr перекинуло бы на второй.
ptr++ == ptr +1
ptr +1 == pa[i++] т.е. мы прыгаем на адрес следующего массива i+1. просто он совпадает с нулевым элементом.

Цитата Сообщение от TRam_ Посмотреть сообщение
Для arr1[1] нужно
тут не было задачи перейти к arr[1] тут была задача показать что есть и адресс массива. ptr++ нас выкидывает за массив а не на следующий элемент как можно вначале подумать.

Добавлено через 16 минут
Цитата Сообщение от alexbmd Посмотреть сообщение
я немного погулил и как понял там три разных типа памяти (heap, const, еще чтото)
Кликните здесь для просмотра всего текста
[вообще страная тенденция, вроде некоторые форумчяне знают ответ но предпочитают молчать или уводят разговор в другое русло или смеются мол я ерунду говорю. а по сути я двигался в верном направлении но тогда понятно не хватало знаний. и знающий форумчянен напрмиер мог сказть третья память стэк или дать еще более развернутый ответ вместо того чтоб смеятся]
поэтому отвечу сам себе

heap, const, stack

но вообще их 5


По поводу области памяти: их вообще 5 — константные данные, стек, свободная динамическая память, куча, глобальная/статическая.

Константные данные: в этой области хранятся строки и другие данные, чьи значения известны во время компиляции. В этой области не могут находиться объекты классов.Все данные из этой области доступны в течение всего времени жизни программы. Кроме того, все данные в этой области памяти доступны только для чтения, и результат попытки их изменения не определен. В частности, это связано с тем, что формат хранения этих данных может быть оптимизирован конкретной реализацией языка. Например, компилятор может оптимизировать хранение строк и разместить их в перекрывающихся объектах. Это объясняет, почему разные компиляторы дают разные результаты.
и почему ни в одной книже по с об этом ни слова
0
16082 / 8684 / 2120
Регистрация: 30.01.2014
Сообщений: 14,969
09.05.2019, 15:35 56
Цитата Сообщение от alexbmd Посмотреть сообщение
и почему ни в одной книже по с об этом ни слова
Информация, которую вы нашли, как раз взята из книги: "Решение сложных задач на C++, - Г. Саттер", глава 6 целиком посвящена управлению памятью. Там же, в частности, поясняется почему куча и свободная динамическая память выделены отдельно.
0
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
09.05.2019, 15:35
Помогаю со студенческими работами здесь

Как очищать TextOut(x,y)?
Привет всем. Подскажите, как очищать TextOut(x,y)? При нажатии кнопки текст выводиться на image1...

Как очищать таблицу?
Суть: Если в таблице больше 10 строк, то удалять самую раннюю строку, то есть которая была...

Как правильно очищать память?
Как правильно очищать память? char *filePath; // инициализируется в другом участке кода. ...

Память освобождается, только если программа работает при char *c, а нужно при char c
С клавиатуры вводится символьная строка (предложение), размещается в памяти, выводится в том же...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
56
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2022, CyberForum.ru