|
Проблема с условием, m = m+I|P == I ? m : 0; |
|
|
|
Aug 20 2018, 08:45
|
Гуру
     
Группа: Свой
Сообщений: 10 713
Регистрация: 11-12-04
Пользователь №: 1 448

|
Цитата(ave! @ Aug 20 2018, 11:18)  Подскажите ссылку где можно поучится этим условиям в контексте моей проблемы. Гуглите "c operator precedence". Надо заметить, что это - Код if (x+r-y|u&32|p > 2&(p-4|j-7 || b[G=x+3^r>>1&7]-k-6 || b[G^1]|b[G^2])) t += p<5; else F = y; - просто прекрасно. В жизни не видел такой красоты в реальных проектах.
|
|
|
|
|
Aug 20 2018, 09:01
|

Гуру
     
Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095

|
QUOTE (ave! @ Aug 20 2018, 11:18)  m = m+I|P == I ? m : 0;
Эта строка выдает предупреждение: "| имеет более низкий приоритет, чем ==; == будет оцениваться сначала" Когда пишу m = (m+I|P) == I ? m : 0; предупреждение исчезает, но я не знаю можно ли так писать, не нарушит ли эта запись условие. Скобки вы расставили неправильно даже несмотря на подсказку компилятора. Если автор исходного кода знал приоритеты операций (а судя по всему он их знал), то скобки должны стоять так: m = ((m + I) | (P == I)) ? m : 0. P.S. В последнее время компиляторы что-то уж больно часто стали ругаться на очевидные выражения. Неужели настолько упал уровень основной массы современных программистов, что они не в состоянии запомнить приоритет операторов? Жду - не дождусь, когда компилятор начнет требовать скобки в выражении a*x+b.
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
|
Aug 20 2018, 09:05
|
Гуру
     
Группа: Свой
Сообщений: 10 713
Регистрация: 11-12-04
Пользователь №: 1 448

|
Цитата(ViKo @ Aug 20 2018, 11:57)  Дык, обфускация. Для обычной обфускации это как-то слишком. Тут что-то болезненное просматривается. Цитата(Сергей Борщ @ Aug 20 2018, 12:01)  P.S. В последнее время компиляторы что-то уж больно часто стали ругаться на очевидные выражения... GCC еще и замены предлагает в гугл-стиле: "а не имели ли Вы, уважаемый, в виду A, а не B?" А ведь кто-то просто последует такой рекомендации не вникая.
|
|
|
|
|
Aug 20 2018, 10:18
|
Знающий
   
Группа: Свой
Сообщений: 771
Регистрация: 16-07-07
Из: Волгодонск
Пользователь №: 29 153

|
Цитата(Сергей Борщ @ Aug 20 2018, 12:01)  Скобки вы расставили неправильно даже несмотря на подсказку компилятора. Если автор исходного кода знал приоритеты операций (а судя по всему он их знал), то скобки должны стоять так: m = ((m + I) | (P == I)) ? m : 0.
P.S. В последнее время компиляторы что-то уж больно часто стали ругаться на очевидные выражения. Неужели настолько упал уровень основной массы современных программистов, что они не в состоянии запомнить приоритет операторов? Жду - не дождусь, когда компилятор начнет требовать скобки в выражении a*x+b. Вот ни разу не очевидное выражение  Плюс что-то кажется мне, что тут правильней не бинарное или, а логическое. А по теме - врядли в IAR и gcc разный приоритет операторов, если проект у вас был рабочий - оставьте как есть, просто отключите эти ворнинги. Если хотите улучшить читаемость - лучше ее отдельно улучшать, после того как перенесенный проект заработает в таком виде
|
|
|
|
|
Aug 20 2018, 10:29
|

Участник

Группа: Участник
Сообщений: 23
Регистрация: 20-05-11
Пользователь №: 65 166

|
Цитата(segment @ Aug 20 2018, 11:55)  Воу. Если не секрет, то что это за проект такой? Это не секрет, это шахматная программа micro-Max. Весь код помещается на одном листе А4, притом, что программа знает даже такое правило, как "взятие пешки на проходе". Мне этот алгоритм очень понравился, я когда-то с друзьями в турнир играл, четырех обыграл, а эта програмка меня взувает (сделал шахматный калькулятор на ATmega48). Тут верно заметили, что код писал математик. Спасибо за советы.
Сообщение отредактировал ave! - Aug 20 2018, 10:42
|
|
|
|
|
Aug 20 2018, 11:25
|

Гуру
     
Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095

|
QUOTE (Непомнящий Евгений @ Aug 20 2018, 13:18)  Вот ни разу не очевидное выражение Приоритет операций известен. Да, оно сложнее, чем a+b, но ничего неоднозначного и тем более неочевидного в нем нет. В a*x+b тоже будем скобки расставлять? QUOTE (Непомнящий Евгений @ Aug 20 2018, 13:18)  Плюс что-то кажется мне, что тут правильней не бинарное или, а логическое. С этим согласен.
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
|
Aug 20 2018, 11:31
|
Гуру
     
Группа: Свой
Сообщений: 5 228
Регистрация: 3-07-08
Из: Омск
Пользователь №: 38 713

|
Цитата(ave! @ Aug 20 2018, 13:29)  Тут верно заметили, что код писал математик. Код писал извращенец человек, цель которого была совсем не оптимальность и не решение практической задачи, а цель - вычурность кода. Ибо: вместо t += p<5 простой программист использует if, а более продвинутый заменит на что-то типа: t -= (int)(p - 5) >> 31;
|
|
|
|
|
Aug 20 2018, 11:36
|

Участник

Группа: Участник
Сообщений: 23
Регистрация: 20-05-11
Пользователь №: 65 166

|
Цитата(Сергей Борщ @ Aug 20 2018, 12:01)  Скобки вы расставили неправильно даже несмотря на подсказку компилятора. Если автор исходного кода знал приоритеты операций (а судя по всему он их знал), то скобки должны стоять так: m = ((m + I) | (P == I)) ? m : 0. Сергей Борщ, я скопировал все 7 предупреждений. Может у вас будет настроение расставить все скобки. Буду вам очень благодарен. Чувствую, что даже после изучения приоритетов я все-равно где-то, что-то не так сделаю. Программа скомпилируется и даже работать будет, но это шахматный алгоритм, там можно не понять что что-то не так работает. (1) while (d++<n||d<3||z&K==I&&(N<T&d<98||(K=X,L=Y&~M,d=3))) (2) while (r=p>2&r<0?-r:-o[++j]) (3) if (t&k|p<3&!(y-x&7)-!t) (4) v-=p-4|R>29?0:20; (5) Y=y|S&F; (6) if (x+r-y|u&32|p>2&(p-4|j-7||b[G=x+3^r>>1&7]-k-6||b[G^1]|b[G^2])) (7) m=m+I|P==I?m:0; Исправление готово: m = ((m+I)|(P==I)) ? m : 0; Мой случай как раз яркий пример для статьи на хабре: Как вы пишете условия в СИ-подобных языках? Со скобками в условиях или без?
|
|
|
|
|
Aug 20 2018, 12:20
|

Участник

Группа: Участник
Сообщений: 23
Регистрация: 20-05-11
Пользователь №: 65 166

|
Приоритет операций C++ - это я себе в тему на заметку. Цитата(jcxz @ Aug 20 2018, 14:47)  Эта конструкция скомпилится в большее число команд, чем простое, читаемое и аналогичное по результату: if (!(m + I) && P != I) m = 0; И в других местах - аналогично. Поэтому единственная цель такого поделия - сделать вычурный код. jcxz, может вы поможете разобраться с остальными местами по списку.
|
|
|
|
|
Aug 20 2018, 13:05
|

Участник

Группа: Участник
Сообщений: 23
Регистрация: 20-05-11
Пользователь №: 65 166

|
Цитата(jcxz @ Aug 20 2018, 15:23)  Мне кажется первая строчка к си не относится. Или там ошибка. Вы там где-то имя функции не забыли? Иначе компилятор должен был ругнуться. Код рабочий, на AVR компилируется без ошибок и предупреждений. Предупреждения только при переносе на STM. На самом деле это с моей стороны извращение пытаться понять этот код. Стиль написания самой функции поиска следующего хода для меня не понятна: short D(q,l,e,E,z,n) short q,l,e; unsigned char E,z,n; { //... } Я подозреваю, что это тоже самое что: short D(short q, short l, short e, unsigned char E, unsigned char z, unsigned char n) { //... } Под AVR компилятор пропустил и первую запись, а вот под STM не прошло.
|
|
|
|
|
Aug 20 2018, 13:24
|
Гуру
     
Группа: Свой
Сообщений: 5 228
Регистрация: 3-07-08
Из: Омск
Пользователь №: 38 713

|
Цитата(ave! @ Aug 20 2018, 16:05)  Стиль написания самой функции поиска следующего хода для меня не понятна: short D(q,l,e,E,z,n) Возможно там макросами что-то переопределено. Цитата(ave! @ Aug 20 2018, 14:36)  (1) while (d++<n||d<3||z&K==I&&(N<T&d<98||(K=X,L=Y&~M,d=3))) Но такое не должно компилиться. имхо. выделенное.
|
|
|
|
|
Aug 20 2018, 13:28
|

Гуру
     
Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095

|
QUOTE (jcxz @ Aug 20 2018, 16:24)  Возможно там макросами что-то переопределено. Это K&R style, поддерживается компиляторами для совместимости с самыми старыми исходниками (созданными еще до принятия Стандарта языка), сейчас практически не применяется и это правильно. QUOTE (jcxz @ Aug 20 2018, 16:24)  Но такое не должно компилиться. имхо. выделенное. Почему? Оператор "запятая" никто не отменял. QUOTE (ave! @ Aug 20 2018, 16:05)  Я подозреваю, что это тоже самое что: Да, именно так.
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
|
Aug 20 2018, 16:12
|
Гуру
     
Группа: Свой
Сообщений: 5 228
Регистрация: 3-07-08
Из: Омск
Пользователь №: 38 713

|
Цитата(yes @ Aug 20 2018, 17:28)  ну дык (по описанию micro-Max) задача стояла минимум символов в коде, поэтому такой код была такая специальная олимпиада по С программированию  , помню печать кода программы в стдаут, например, но чтобы шахматы - это охренеть А я вот помню что когда-то умудрялись написать программу в ASCII-символах. Т.е. - файл программы состоял только из байтов печатаемого диапазона ASCII-кодов: 33...126. Отсылаешь такую прогу как обычный текст, сохраняешь в файл, переименовываешь его в расширение .com и запускаешь Без всякого base- или uue-кодирования. А если применительно к МК, то никакого .hex тогда не надо, .bin вполне текстовый был бы.
|
|
|
|
|
Aug 22 2018, 13:55
|

Гуру
     
Группа: Модераторы
Сообщений: 8 455
Регистрация: 15-05-06
Из: Рига, Латвия
Пользователь №: 17 095

|
QUOTE (Arlleex @ Aug 22 2018, 15:33)  А еще результат выражения != 0 не обязательно есть 1. Обязательно есть. QUOTE (Arlleex @ Aug 22 2018, 15:33)  Поэтому тот, кто писал t += p<5, явно чудак  Тот, кто писал этот код, очень хорошо знал Стандарт языка. В отличие от вас. QUOTE 6.5.8 Relational operators 6 Each of the operators < (less than), > (greater than), <= (less than or equal to), and >= (greater than or equal to) shall yield 1 if the specified relation is true and 0 if it is false.89) The result has type int. 6.5.9 Equality operators The == (equal to) and != (not equal to) operators are analogous to the relational operators except for their lower precedence.90) Each of the operators yields 1 if the specified relation is true and 0 if it is false. The result has type int.
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
|
Aug 25 2018, 07:08
|

Местный
  
Группа: Участник
Сообщений: 492
Регистрация: 12-11-11
Пользователь №: 68 264

|
Цитата(Сергей Борщ @ Aug 22 2018, 16:55)  Обязательно есть. Тот, кто писал этот код, очень хорошо знал Стандарт языка. В отличие от вас. Вон оно как? Стандартов не читал, да, признаю. Но вот будет ли так в дальнейшем - не совсем ясно, поэтому так писать лично я бы не стал. Ведь компиляторы все новее и новее, а оптимизация все хитрее. Никто не знает, во что это выльется в будущем.
|
|
|
|
|
Aug 25 2018, 07:21
|
Гуру
     
Группа: Свой
Сообщений: 5 228
Регистрация: 3-07-08
Из: Омск
Пользователь №: 38 713

|
Цитата(Arlleex @ Aug 25 2018, 10:08)  Но вот будет ли так в дальнейшем - не совсем ясно, поэтому так писать лично я бы не стал. Ведь компиляторы все новее и новее, а оптимизация все хитрее. Никто не знает, во что это выльется в будущем. Я тоже выше об этом писал. Понятно что сейчас это работает. Но такой подход и стиль считаю плохим. char тоже раньше всегда был знаковым, а сейчас может быть и беззнаковым, в зависимости от установок компилятора. Всё потому, что в появились некоторые CPU, в которых чтение/запись беззнаковых char короче чем знаковых, и во многих случаях, когда знаковость не важна, лучше использовать такие операции чтения/записи. Так и с этим - если CPU позволяет более эффективно работать с типами bool со значениями 0/-1, а не 0/1, то лучше так и делать, разрешив спец.опцией компилятора. Хотя конечно это гораздо менее часто встречающаяся потребность чем char, значит не очень важная.
|
|
|
|
|
Aug 25 2018, 07:39
|

Местный
  
Группа: Участник
Сообщений: 492
Регистрация: 12-11-11
Пользователь №: 68 264

|
Цитата(jcxz @ Aug 25 2018, 10:21)  Я тоже выше об этом писал. Понятно что сейчас это работает. Но такой подход и стиль считаю плохим. char тоже раньше всегда был знаковым, а сейчас может быть и беззнаковым, в зависимости от установок компилятора. Про CPU с различными подходами в копировании char не знал, спасибо. Интересно конечно, как это может так быть - ведь копируются 8 бит все равно... А вот размышления вслух, можно сказать. Допустим есть условие if(x != y) или просто выражение z = (x != y). Переменные целые. Я бы на месте компилятора сделал что-то наподобие z = x - y или z = x ^ y. И если z равно 0, то это значило, что x == y. А если не равно 0, то, соответственно, x != y. Вот типичный случай, когда не нужны были бы лишние телодвижения по установке значения 1 в переменную вместо прямого присваивания результата вычитания. Меньше инструкций ведь по факту. И таких примеров оптимизаций, ИМХО, можно как-то еще придумать. Опять же, просто исходя из логики рассуждаю
Сообщение отредактировал Arlleex - Aug 25 2018, 07:42
|
|
|
|
|
Aug 25 2018, 09:23
|

Местный
  
Группа: Участник
Сообщений: 492
Регистрация: 12-11-11
Пользователь №: 68 264

|
Цитата(jcxz @ Aug 25 2018, 10:59)  Загляните как-нить в листинг Cortex-M3/4 кода и сравните команды LDRB и LDRSB например. Поймёте почему. Для этого имхо и ввели дополнительный чекбокс в IAR "signed/unsigned char". Сходу не получилось заставить компилятор сгенерировать LDRSB... Использую armcc в составе Keil. Оптимизация 0. Галка "Plain Char is Signed" сброшена. Код Код volatile signed char a = 10; volatile signed char b = 20; volatile signed char c;
int main(void) { c = a - b; while(1); } формирует следующий листинг Код 0x080002E0 4805 LDR r0,[pc,#20] 0x080002E2 7800 LDRB r0,[r0,#0x00]; загрузили a 0x080002E4 4905 LDR r1,[pc,#20] 0x080002E6 7809 LDRB r1,[r1,#0x00]; загрузили b 0x080002E8 1A40 SUBS r0,r0,r1 ; вычли 0x080002EA B240 SXTB r0,r0 ; ИМХО, лишнее, т.к. SUBS работает 32-битными числами 0x080002EC 4904 LDR r1,[pc,#16] 0x080002EE 7008 STRB r0,[r1,#0x00]; загружаем результат в переменную c В случае, если переменные объявить без спецификатора signed (то есть с unsigned) то листинг тот же самый, но без SXTB С оптимизацией -O3 для signed/unsigned листинг одинаковый: Код 0x0800028C 4803 LDR r0,[pc,#12] 0x0800028E 7801 LDRB r1,[r0,#0x00] 0x08000290 7842 LDRB r2,[r0,#0x01] 0x08000292 1A89 SUBS r1,r1,r2 0x08000294 7081 STRB r1,[r0,#0x02]
Сообщение отредактировал Arlleex - Aug 25 2018, 09:33
|
|
|
|
|
Aug 25 2018, 09:59
|
Гуру
     
Группа: Свой
Сообщений: 5 228
Регистрация: 3-07-08
Из: Омск
Пользователь №: 38 713

|
Цитата(Arlleex @ Aug 25 2018, 12:23)  Сходу не получилось заставить компилятор сгенерировать LDRSB... Использую armcc в составе Keil. Оптимизация 0. Галка "Plain Char is Signed" сброшена. Всего лишь оптимизация умного компилятора Вы сами подумайте: раз Вы результат обратно сокращаете до 8 бит, то зачем тогда старшие биты вычислять? А если их вычислять не нужно, то можно ограничиться командами LDRB как более "дешёвыми". Тест Ваш построен некорректно. Для корректности теста сделайте int volatile c. Тогда, думаю, увидите LDRSB. Ну и для теста можно одно из a или b сделать отрицательным. Цитата(Arlleex @ Aug 25 2018, 12:23)  0x080002EA B240 SXTB r0,r0 ; ИМХО, лишнее, т.к. SUBS работает 32-битными числами Хоть это и лишнее, но это был намёк компилятора, что он заметил достаточность использования 8-бит для результата и поэтому не стал заморачиваться с правильным расширением входных аргументов.  PS: Код теста не совсем корректный, так как должен вызвать warning "undefined behavior".
|
|
|
|
|
Aug 25 2018, 10:24
|

Местный
  
Группа: Участник
Сообщений: 492
Регистрация: 12-11-11
Пользователь №: 68 264

|
Цитата(jcxz @ Aug 25 2018, 12:59)  Тест Ваш построен некорректно. Для корректности теста сделайте int volatile c. Тогда, думаю, увидите LDRSB. Точно! И вправду LDRB заменились на LDRSB... Просто с расширением знака. Все четко по описанию команды загрузки с расширением знака Единственное, что отмечу. Разницы в количестве тактов выполнения не заметно... То есть по факту выполняются они одинаково по времени, только лишь представление числа в памяти нужным образом расширяется или нет, вот и вся разница, вроде как. Цитата(jcxz @ Aug 25 2018, 12:59)  PS: Код теста не совсем корректный, так как должен вызвать warning "undefined behavior". Из-за int перед main() и отсутствия возвращаемого результата? Тогда не неопределенное поведение, а что-то типа "недостижимый участок кода" и "отсутствие возвращаемого значения в функции, которая возвращает int"... Но Keil не паникует, кстати...
|
|
|
|
|
Aug 25 2018, 12:24
|
Гуру
     
Группа: Свой
Сообщений: 5 228
Регистрация: 3-07-08
Из: Омск
Пользователь №: 38 713

|
Цитата(Arlleex @ Aug 25 2018, 13:24)  Единственное, что отмечу. Разницы в количестве тактов выполнения не заметно... То есть по факту выполняются они одинаково по времени, только лишь представление числа в памяти нужным образом расширяется или нет, вот и вся разница, вроде как. Конечно разницы в тактах нет при условии наличия команды в кеше/конвеере CPU. Но в реальном МК команду ещё нужно выбрать из памяти, а ширина шины к памяти программ не бесконечна, как и скорость последней. Так что по времени выполнения - Вы не правы. Цитата(Arlleex @ Aug 25 2018, 13:24)  Из-за int перед main() и отсутствия возвращаемого результата? Нет. Из-за того, что в одном выражении используются две volatile-переменные. Порядок чтения которых внутри выражения неопределён. А volatile подразумевает строгость в порядке доступа. Цитата(Arlleex @ Aug 25 2018, 13:24)  Но Keil не паникует, кстати...  А зря. IAR реагирует на подобные выражения: "undefined behavior".
|
|
|
|
|
Aug 25 2018, 13:23
|

Местный
  
Группа: Участник
Сообщений: 492
Регистрация: 12-11-11
Пользователь №: 68 264

|
Цитата(jcxz @ Aug 25 2018, 15:24)  Конечно разницы в тактах нет при условии наличия команды в кеше/конвеере CPU. Но в реальном МК команду ещё нужно выбрать из памяти, а ширина шины к памяти программ не бесконечна, как и скорость последней. Так что по времени выполнения - Вы не правы. Открыл TRM на Cortex-M3. Вижу, что CPI на LDRB и на LDRSB одинаковы и составляют 2 такта. Одинакова и их синтаксическая запись. Существуют как 16-битные, так и 32-битные версии обеих команд. При всех прочих равных условиях время выполнения LDRB и LDRSB одинаково. То, что команду нужно выбрать из памяти - это понятно. Но LDRB нужно выбрать точно так же, как и LDRSB в одном и том же коде. Ничем не отличается. Другое дело, если мы говорим о том, что загружать значение как оно есть и лишь потом программно еще расширять знак. Там да, несколько команд нужно: тот же LDRB + расширение знака, если требуется. Цитата(jcxz @ Aug 25 2018, 15:24)  Нет. Из-за того, что в одном выражении используются две volatile-переменные. Порядок чтения которых внутри выражения неопределён. А volatile подразумевает строгость в порядке доступа. Да, в упор не увидел очевидное... Жаль Keil не ругнулся никак даже при установке "All Warnings" в настройках компилятора
Сообщение отредактировал Arlleex - Aug 25 2018, 13:24
|
|
|
|
|
Aug 25 2018, 13:49
|
Гуру
     
Группа: Свой
Сообщений: 5 228
Регистрация: 3-07-08
Из: Омск
Пользователь №: 38 713

|
Цитата(Arlleex @ Aug 25 2018, 16:23)  Открыл TRM на Cortex-M3. Вижу, что CPI на LDRB и на LDRSB одинаковы и составляют 2 такта. Одинакова и их синтаксическая запись. Существуют как 16-битные, так и 32-битные версии обеих команд. При всех прочих равных условиях время выполнения LDRB и LDRSB одинаково. А Вы когда-нибудь пробовали заглядывать в листинг? И сколько вы там видели LDRB разной длины и сколько LDRSB? Формы-то может и есть, тут вопрос в количестве.... Попробуйте в .asm-файле ввести 2 такие команды и скомпилить: Код LDRB R0, [R0, #0] LDRSB R0, [R0, #0] А потом попробуйте поизменять регистры от R0 до R7 и посмотреть что получается. А теперь загляните в листинги и увидите, что большинство обращений к памяти как раз подобные формы команд: косвенная с константным смещением.
|
|
|
|
|
Aug 25 2018, 14:15
|

Местный
  
Группа: Участник
Сообщений: 492
Регистрация: 12-11-11
Пользователь №: 68 264

|
Цитата(jcxz @ Aug 25 2018, 16:49)  А Вы когда-нибудь пробовали заглядывать в листинг? И сколько вы там видели LDRB разной длины и сколько LDRSB? Формы-то может и есть, тут вопрос в количестве.... Мда, в листинге действительно LDRSB используются всегда 32-битные, очевиден проигрыш... Причем какие бы регистры не использовали - младшие или старшие - все равно берется команда Thumb-2 (32 бит). А вот LDRB, при использовании младших регистров в качестве операндов, компилируется в 16-битной Thumb форме; при использовании старших регистров - законно Thumb-2. P.S. Почитал тут в мануале. Если запись команды LDRSB использует форму с непосредственным смещением от базового регистра, то такая команда существует только 32-битная. Для LDRB такая команда есть в 16 битах, только там непосредственное значение ограничено 5 битами. Теперь все понятно! jcxz, благодарю за полезную информацию
|
|
|
|
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|