Форум программистов, компьютерный форум, киберфорум
С++ для начинающих
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.88/8: Рейтинг темы: голосов - 8, средняя оценка - 4.88
 Аватар для koker007
59 / 52 / 11
Регистрация: 04.08.2015
Сообщений: 731

Неправильный доступ к union

15.06.2017, 14:21. Показов 1713. Ответов 18
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Здравствуйте, я не совсем понимаю как работает и как устроен union. Его я использую в пакетах для передачи конкретной информации, какая именно хранится в union - известно. Но все манипуляции с ним производятся по адресу в памяти.

У меня к сожалению не получается использовать его данные по указателю по какой-то причине пишется что выхожу за пределы этой области.

В общем само объединение такое:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
    union data_socket {
    public:
        TDM_1_connect data1;
        TDM_2_player data2;
        TDM_3_message_chat data3;
        TDM_4_message_server data4;
        TDM_5_timer_to_start data5;
 
        data_socket() {};
        ~data_socket() {};
 
    };
    data_socket data_sock;
Объединение объявляется в классе и доступ к данным объединения осуществляется по методу
C++
1
2
3
4
5
6
7
8
9
    void* get_TDM_pointer() {
        if (parametr.TDM_type == 1) {return &data_sock.data1;   }
        else if(parametr.TDM_type == 2) { return &data_sock.data2;  }
        else if(parametr.TDM_type == 3) { return &data_sock.data3; }
        else if (parametr.TDM_type == 4) { return &data_sock.data4; }
        else if (parametr.TDM_type == 5) { return &data_sock.data5; }
        else { return 0; }
        //else if (parametr.TDM_type == 6) { return &data_sock.data6; }
    }
где parametr.TDM_type это тип данных он заранее известен.

Ошибка происходит в этом участке
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
26
27
28
29
30
31
32
33
34
35
36
37
    //Отправить пакет с запросом на вход к серверу
    void send_packet() {
 
        string ip_to_server = GameSetup_1.Get_ip_connect();
 
        conection_data* connect_data = 0;
 
        //получаем соединение для работы с ним
        if (out_pack.its_new_connection_yn(ip_to_server)) {
            connect_data = out_pack.create_new_connection(ip_to_server, 45666);
        }
        else {
            connect_data = out_pack.get_pointer_connection(ip_to_server);
        }
 
        //получили соединение теперь создаем пакет
        packet* pack = connect_data->get_new_packet();
 
        //Пакет получен теперь заполняем
 
        if (pack != 0){
 
            //Получаем указатель на начало данных
            TDS_main* TDS_data = pack->get_data_main_pointer();
 
            //Говорим что тип данных 1 - приветсвие
            TDS_data->set_TDM_type(1);
            void* TDM_data = TDS_data->get_TDM_pointer();
 
            GLshort image = GameSetup_1.Get_z_image();
 
            ((TDM_1_connect*)TDM_data)->set_player_name(GameSetup_1.Get_nickname());
            ((TDM_1_connect*)TDM_data)->set_id_image(image);
            ((TDM_1_connect*)TDM_data)->set_color(GameSetup_1.Get_color_player());
 
        }
    }
А именно, когда выполняется
((TDM_1_connect*)TDM_data)->set_player_name(GameSetup_1.Get_nicknam e());
то есть по факту скорей всего передается не правильный адрес TDM_data
как я уже сказал я не совсем понимаю как хранятся данные в union, чтобы правильно его использовать и ссылаться на них. Все что знаю это то что он имеет размер самого большого из перечисленных типов.
Пожалуйста укажите на ошибку, мне надо чтобы функции где ((TDM_1_connect*)TDM_data) приняли адрес TDM_data.
0
Лучшие ответы (1)
IT_Exp
Эксперт
34794 / 4073 / 2104
Регистрация: 17.06.2006
Сообщений: 32,602
Блог
15.06.2017, 14:21
Ответы с готовыми решениями:

union как определить из какой таблице запись после UNION?
Подскажите пожалуйста! Вот например две таблице (TEBLE_1) у которой поля row_1(BIGINT) и (TABLE_2) у которой поля row_2(TEXT) Я ИХ...

Неправильный логин и неправильный пароль, программа не выдает сообщения об ошибке
Вообщем проблема такова: DBConnect->ConnectionString = "Provider=SQLOLEDB.1;Password=" + password + ";Persist Security...

Неправильный парсинг строки и неправильный её вывод
Добрый день! Столкнулся с непонятным поведением парсера: На вход подается вот такая строка: Далее, я делаю ее парсинг вот...

18
Одессит
 Аватар для kylroma
243 / 88 / 44
Регистрация: 30.12.2013
Сообщений: 316
Записей в блоге: 2
15.06.2017, 14:30
Проверьте указатель void* TDM_data на NULL, пред тем, как использовать.
0
зомбяк
 Аватар для TRam_
1585 / 1219 / 345
Регистрация: 14.05.2017
Сообщений: 3,940
15.06.2017, 14:48
Цитата Сообщение от koker007 Посмотреть сообщение
как я уже сказал я не совсем понимаю как хранятся данные в union
Хранится там только один набор данных, который может интерпретироваться как любая из указанных в union структур. То есть вместо того, чтоб писать
C++
1
reinterpret_cast<TDM_3_message_chat *>(&data_sock)
можно написать
C++
1
&(data_sock.data3)
Но потому делать как тут
C++
1
2
3
if (parametr.TDM_type == 1) {return &data_sock.data1;   }
        else if(parametr.TDM_type == 2) { return &data_sock.data2;  }
        else if(parametr.TDM_type == 3) { return &data_sock.data3; }
бессмысленно. Достаточно было бы сделать так:

C++
1
2
3
4
5
void* get_TDM_pointer() {
  if((parametr.TDM_type <= 0) || (parametr.TDM_type > 5))
    return 0;
  return &data_sock;
}
А соответственно вместо
C++
1
((TDM_1_connect*)TDM_data)->set_id_image(image);
нужно
C++
1
data_sock->data1.set_id_image(image);
1
 Аватар для koker007
59 / 52 / 11
Регистрация: 04.08.2015
Сообщений: 731
16.06.2017, 15:06  [ТС]
Цитата Сообщение от TRam_ Посмотреть сообщение
Достаточно было бы сделать так:
Спасибо, сделал так:
C++
1
2
3
    void* get_TDM_pointer() {
        return &data_sock;
    }
Цитата Сообщение от kylroma Посмотреть сообщение
Проверьте указатель void* TDM_data на NULL
Проверил, не равно нулю.

Цитата Сообщение от TRam_ Посмотреть сообщение
А соответственно вместо
((TDM_1_connect*)TDM_data)->set_id_image(image);
нужно
data_sock->data1.set_id_image(image);
Использовать в открытую data_sock не получится т.к. это объединение объявляется внутри класса. Типы используемые в этом объединении - глобальные и при обращении по ссылке известно какому типу будет принадлежать ссылка. Но почему то все еще не получается изменить объект по ссылке. Я думал что проблемма в том что я не правильно использую union а именно из за не понимания как хранятся данные ошибаюсь в том как беру ссылку на объект объединения, но раз вы говорите можно ссылаться напрямую на само объединение, то похоже что проблема в другом.

Добавлено через 23 часа 35 минут
В общем решил сделать определение объединения - глобальным, а в классе просто создать экземпляр этого объединения, а метод возвращает адрес этого экземпляра объединения однако все равно возникает ошибка "нарушения прав доступа при записи" Что не так - не понимаю, вроде создается экземпляр объединения, по методу возвращается адрес на него, а при изменении значений этого экземпляра по адресу - ошибка доступа при записи.

Проблемное объединение к которому нужно обратиться и не получается
C++
1
2
3
4
5
6
7
8
9
10
11
12
//Общее типовое объединение Сообщений
union TDM_all_data {
    TDM_1_connect data1;
    TDM_2_player data2;
    TDM_3_message_chat data3;
    TDM_4_message_server data4;
    TDM_5_timer_to_start data5;
 
    TDM_all_data() {}
    ~TDM_all_data() {}
 
};
Класс информации пакета
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
26
27
28
29
30
31
//Хранит информацию о переданном сообщении
class TDS_main {
private:
    struct parametrs {
        //Тип передаваемой информации
        GLchar TDM_type = 0;
        GLushort TDM_size = 0;
        void * TDM_pointer = 0;
 
 
    };
    parametrs parametr;
 
    TDM_all_data TDM_data;
 
public:
    TDS_main() {}
    ~TDS_main() {}
 
    GLchar get_TDM_type() {
        return parametr.TDM_type;
    }
    void set_TDM_type(GLchar func_num_TDM) {
        parametr.TDM_type = func_num_TDM;
    }
 
    TDM_all_data* get_TDM_pointer() {
        return &TDM_data;
    }
 
};
Класс самого пакета к которому обращаемся для получения адреса к его информации (к проблемному объединению)
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
//класс пакета который отправляется
class packet {
private:
    //Возвращает ссылку на количество созданных пакетов
    GLuint& get_num_packet() { static GLuint num_packet = 0; return num_packet; }
 
    struct parametrs {
        GLushort port = 0;
        string ip;
        GLushort id;
        GLchar type_packet = 'o';
        GLbyte ready = 0;
        GLushort num_send = 0;
 
        TDS_main data_main;
        TDS_accept data_accept;
 
        parametrs() {}
        ~parametrs() {}
    };
    parametrs parametr;
 
 
 
    //время создания пакета, чтобы получатель мог смерить, пришел новый результат или устаревший
    struct time_create_packet {
        GLuint min = 0;
        GLushort sec = 0;
        GLdouble milisec_frame = 0;
    };
    time_create_packet time_pack;
 
public:
    packet(GLushort func_TDM_data_type, string func_ip, GLushort func_port, TDS_accept func_TDS_accept) {
        parametr.ip = func_ip;
        parametr.port = func_port;
 
        ++get_num_packet();
        parametr.id = get_num_packet();
 
        //Должен скопировать номера принятых пакетов во внутренний экземпляр TDS_accept
        parametr.data_accept = func_TDS_accept;
 
        parametr.data_main.set_TDM_type(func_TDM_data_type);
 
        //Запоминаем время создания
        time_pack.min = timer_game.Get_time_work_program_minut();
        time_pack.sec = timer_game.Get_time_work_program_sec();
        time_pack.milisec_frame = timer_game.Get_time_frame();
    }
 
 
    //Получить указатель на данные для работы с ними
    TDM_all_data* get_TDM_data_main_pointer() {
        return parametr.data_main.get_TDM_pointer();
    }
    GLushort get_TDM_data_type() {
        return parametr.data_main.get_TDM_type();
    }
 
    GLushort get_id_packet() {
        return parametr.id;
    }
 
    string get_ip() {
        return parametr.ip;
    }
    GLushort get_port() {
        return parametr.port;
    }
 
    GLbyte get_ready() {
        return parametr.ready;
    }
 
    GLuint get_time_create_min() {
        return time_pack.min;
    }
 
    GLushort get_time_create_sec() {
        return time_pack.sec;
    }
 
    GLdouble get_time_create_milisec_frame() {
        return time_pack.milisec_frame;
    }
 
};
И участок кода который вызывает ошибку
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
        //получили соединение теперь создаем пакет
        //Говорим что тип данных 1 - приветсвие
        packet* pack = connect_data->get_new_packet(1);
 
 
        //Пакет получен теперь заполняем
 
        if (pack != 0){
 
            //Получаем указатель на начало данных
            TDM_all_data* TDM_data = pack->get_TDM_data_main_pointer();
 
            GLshort image = GameSetup_1.Get_z_image();
 
            TDM_data->data1.set_player_name(GameSetup_1.Get_nickname());
            TDM_data->data1.set_id_image(image);
            TDM_data->data1.set_color(GameSetup_1.Get_color_player());
 
        }
Проблема при записи в TDM_data->data1.set_player_name(GameSetup_1.Get_n ickname());
0
19491 / 10097 / 2460
Регистрация: 30.01.2014
Сообщений: 17,805
16.06.2017, 15:35
Цитата Сообщение от koker007 Посмотреть сообщение
C++
1
2
3
4
5
TDM_1_connect data1; 
TDM_2_player data2; 
TDM_3_message_chat data3; 
TDM_4_message_server data4; 
TDM_5_timer_to_start data5;
А что это за типы? Уж не классы ли?

Добавлено через 12 минут
Цитата Сообщение от koker007 Посмотреть сообщение
я не совсем понимаю как работает и как устроен union.
Тогда не надо его использовать пока не разберешься. Все проблемы от этого.
Если мое предположение верно, то ты запихал в union объекты классов. А это требует крайней аккуратности. Нужно вручную вызывать корректные конструкторы и деструкторы в соответствии с фактическим хранимым типом. При этом, если тип меняется, нужно корректно уничтожить объект прежнего типа, а только затем создать новый. В общем, если нет уверенности в том как это делать, то лучше вообще не использовать union.
Используй boost::variant/std::variant, либо классический полиморфизм с виртуальными функциями.
2
807 / 534 / 158
Регистрация: 27.01.2015
Сообщений: 3,017
Записей в блоге: 1
16.06.2017, 15:37
koker007, а зачем юзать union, юзай boost variant
1
 Аватар для koker007
59 / 52 / 11
Регистрация: 04.08.2015
Сообщений: 731
16.06.2017, 16:34  [ТС]
Цитата Сообщение от DrOffset Посмотреть сообщение
Нужно вручную вызывать корректные конструкторы и деструкторы в соответствии с фактическим хранимым типом.
Спасибо думаю проблема именно в этом, скажите я правильно понял что.. перед использованием (чтением или записью) объектов классов объявленных мною в union, нужно сначала просто вызвать конструктор этого самого объекта и только?

Видимо по умолчанию он не может вызывать, потому что при инициализации объктов в union, конструкторы этих объектов просто переписывают друг другу данные из-за того что пространство общее, и чтобы это исправить надо показательно вызвать конструктор используемого объекта. Верно?
0
зомбяк
 Аватар для TRam_
1585 / 1219 / 345
Регистрация: 14.05.2017
Сообщений: 3,940
16.06.2017, 16:39
koker007, вначале по тому адресу должны прийти данные из сокета, чтобы с ними работать.
Цитата Сообщение от koker007 Посмотреть сообщение
Его я использую в пакетах для передачи конкретной информации, какая именно хранится в union - известно.
ну вот эта информация должна в точности соответствовать структуре того типа, с которым пытаешься работать.
1
19491 / 10097 / 2460
Регистрация: 30.01.2014
Сообщений: 17,805
16.06.2017, 17:22
Цитата Сообщение от koker007 Посмотреть сообщение
Верно?
В целом да.
Также стоит подумать о том, как сохранять информацию об активном в данный момент поле union.
1
Форумчанин
Эксперт CЭксперт С++
 Аватар для MrGluck
8216 / 5047 / 1437
Регистрация: 29.11.2010
Сообщений: 13,453
16.06.2017, 17:32
Обычно для этого делают enum.
Про std::variant уже вроде бы написали (он использует RTTI для определения типа)
1
 Аватар для koker007
59 / 52 / 11
Регистрация: 04.08.2015
Сообщений: 731
16.06.2017, 18:22  [ТС]
Цитата Сообщение от DrOffset Посмотреть сообщение
В целом да.
У меня к сожалению не получается явно вызвать конструктор, это можно как-то сделать? А то везде пишут что это нельзя потому что он по умолчанию вызывается когда объект создается, но как тут быть если объекты друг друга переписывают?

Цитата Сообщение от MrGluck Посмотреть сообщение
Про std::variant уже вроде бы написали
Да.. спасибо, почитаю.
0
Форумчанин
Эксперт CЭксперт С++
 Аватар для MrGluck
8216 / 5047 / 1437
Регистрация: 29.11.2010
Сообщений: 13,453
16.06.2017, 18:29
union вообще для не POD типов лучше не использовать.
1
19491 / 10097 / 2460
Регистрация: 30.01.2014
Сообщений: 17,805
16.06.2017, 18:56
Цитата Сообщение от koker007 Посмотреть сообщение
это можно как-то сделать?
Для пересоздания - placement new. Про деструктор тоже не забываем. Безопасность исключений тоже по-хорошему надо обеспечивать.
Если объект создается впервые, можно использовать список инициализации конструктора в конструкторе union.

Еще раз повторю, чтобы корректный код здесь написать, нужно быть предельно аккуратным.
1
 Аватар для koker007
59 / 52 / 11
Регистрация: 04.08.2015
Сообщений: 731
17.06.2017, 10:41  [ТС]
Хорошо, решил сделать через boost::variant
Написал так:
C++
1
boost::variant<TDM_1_connect, TDM_2_player, TDM_3_message_chat, TDM_4_message_server, TDM_5_timer_to_start> TDM_data;
Объясните пожалуйста, как у TDM_data вызвать функции класса TDM_1_connect? он ведь уже понимает что принадлежит классу TDM_1_connect и потенциально другим объявленным?
0
19491 / 10097 / 2460
Регистрация: 30.01.2014
Сообщений: 17,805
17.06.2017, 12:36
Лучший ответ Сообщение было отмечено koker007 как решение

Решение

koker007,
C++
1
2
3
TDM_1_connect & connect = boost::get<TDM_1_connect>(TDM_data);
connect.set_player_name(GameSetup_1.Get_nickname());
// и т.д.
1
 Аватар для koker007
59 / 52 / 11
Регистрация: 04.08.2015
Сообщений: 731
18.06.2017, 21:35  [ТС]
Спасибо, получилось отправить и получить данные, но при использовании boost::get с другими раннее объявленными классами в:
C++
1
2
3
4
5
    boost::variant< TDM_1_connect, 
                    TDM_2_player, 
                    TDM_3_message_chat, 
                    TDM_4_message_server, 
                    TDM_5_timer_to_start> TDM_data;
вылезает ошибка.

В общем с помощью boost::get получается получить доступ только к данным типа TDM_1_connect.
С остальными, функция boost::get вызывает ошибку.

Рабочий код:
C++
1
TDM_1_connect& TDM_1_data = boost::get<TDM_1_connect>(TDM_data->TDM_data);
Вызывающие ошибку:
C++
1
2
TDM_2_player& TDM_2_data = boost::get<TDM_2_player>(TDM_data->TDM_data);
TDM_3_message_chat& TDM_2_data = boost::get<TDM_3_message_chat>(TDM_data->TDM_data);
В чем может быть причина? библиотеку boost в первый раз использую.
Сейчас скрин ошибки загружу..
0
 Аватар для koker007
59 / 52 / 11
Регистрация: 04.08.2015
Сообщений: 731
18.06.2017, 21:41  [ТС]
0
19491 / 10097 / 2460
Регистрация: 30.01.2014
Сообщений: 17,805
18.06.2017, 22:10
Цитата Сообщение от koker007 Посмотреть сообщение
В чем может быть причина?
Ну во-первых очень плохо, что ты не перехватываешь исключения...
Во-вторых, ошибка эта из-за того, что сейчас в varinat`е активен объект класса TDM_1_connect (т.к. он первый в списке и поэтому создался по умолчанию), естественно, что попытка чтения его как объекта другого класса будет пресечена (ты должен быть благодарен библиотеке, за то, что она бьет по рукам за такое - в случае union было бы молчаливое согласие и UB в итоге). Чтобы get сработал на других объектах, нужно один из них сперва активировать
C++
1
2
3
// например, так:
TDM_data->TDM_data = TDM_2_player();
// при этом TDM_1_connect будет уничтожен
- ведь они делят общую память, значит одновременно к больше, чем одному объекту, нельзя иметь доступ.
1
 Аватар для koker007
59 / 52 / 11
Регистрация: 04.08.2015
Сообщений: 731
18.06.2017, 22:28  [ТС]
Спасибо, еще раз. Я просто думал что функция get "безопасная", в том плане что прежде чем выдавать данные она их сама проверяет на то создан ли уже объект такого типа.

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

Конечно, без проверок, сам себе яму копаешь)
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
BasicMan
Эксперт
29316 / 5623 / 2384
Регистрация: 17.02.2009
Сообщений: 30,364
Блог
18.06.2017, 22:28
Помогаю со студенческими работами здесь

union
Реализовать структуру «Машина» (цвет, модель, номер). Номер машины может представлять из себя или пятизначный номер или слово до 8...

C++ union на C#
Каким образом это можно сделать в C#. Необязательно,чтобы это была структура,просто может как-то проще можно? Я просто себе уже голову...

Union
Здравствуйте. Нужно сортировать записи в 2х таблицах по дате. Следующий запрос все делает как надо. SELECT date FROM a ...

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

C++ Union в C#
Здравствуйте! Пишу программу на C# и столкнулся с проблемой, что dll написанная на с++ и использует Union структуру. Помогите,...


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

Или воспользуйтесь поиском по форуму:
19
Ответ Создать тему
Новые блоги и статьи
Кто-нибудь знает, где можно бесплатно получить настольный компьютер или ноутбук? США.
Programma_Boinc 26.12.2025
Нашел на реддите интересную статью под названием Anyone know where to get a free Desktop or Laptop? Ниже её машинный перевод. После долгих разбирательств я наконец-то вернула себе. . .
Thinkpad X220 Tablet — это лучший бюджетный ноутбук для учёбы, точка.
Programma_Boinc 23.12.2025
Рецензия / Мнение/ Перевод Нашел на реддите интересную статью под названием The Thinkpad X220 Tablet is the best budget school laptop period . Ниже её машинный перевод. Thinkpad X220 Tablet —. . .
PhpStorm 2025.3: WSL Terminal всегда стартует в ~
and_y87 14.12.2025
PhpStorm 2025. 3: WSL Terminal всегда стартует в ~ (home), игнорируя директорию проекта Симптом: После обновления до PhpStorm 2025. 3 встроенный терминал WSL открывается в домашней директории. . .
Как объединить две одинаковые БД Access с разными данными
VikBal 11.12.2025
Помогите пожалуйста !! Как объединить 2 одинаковые БД Access с разными данными.
Новый ноутбук
volvo 07.12.2025
Всем привет. По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне: Ryzen 5 7533HS 64 Gb DDR5 1Tb NVMe 16" Full HD Display Win11 Pro
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
Ломающие изменения в C#.NStar Alpha
Etyuhibosecyu 20.11.2025
Уже можно не только тестировать, но и пользоваться C#. NStar - писать оконные приложения, содержащие надписи, кнопки, текстовые поля и даже изображения, например, моя игра "Три в ряд" написана на этом. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru