|
Как правильно создать многофайловый проект, Ошибка при линковке |
|
|
3 страниц
1 2 3 >
|
 |
Ответов
(1 - 31)
|
May 6 2007, 14:24
|

Гуру
     
Группа: Свой
Сообщений: 13 372
Регистрация: 27-11-04
Из: Riga, Latvia
Пользователь №: 1 244

|
Цитата(alux @ May 6 2007, 17:07)  void battery_charge(void) { FAST_charge(); } Описка. Найдите разницу в одной букве: Цитата void FAST_carge (void) { } И пожалуй не стоит писать в AVR->IAR, ибо сие ни к AVR ни к IAR отнрошения не имеет. P.S. И дублировать прототипы функций в headers это лишнее.
--------------------
Feci, quod potui, faciant meliora potentes
|
|
|
|
|
May 6 2007, 14:46
|
Знающий
   
Группа: Свой
Сообщений: 589
Регистрация: 24-04-05
Пользователь №: 4 447

|
Цитата(jorikdima @ May 6 2007, 17:20)  хидеры то подключены, а в опциях проекта пути к ним прописаны? (если они по поддиректориям распределены) Все файлы (*.с, *.h) лежат в одной рабочей папке. Цитата(zltigo @ May 6 2007, 17:24)  И дублировать прототипы функций в headers это лишнее. Ошибся при наборе сообщения, уже исправил...
|
|
|
|
|
Dec 9 2007, 19:06
|
Гуру
     
Группа: Свой
Сообщений: 2 712
Регистрация: 28-11-05
Из: Беларусь, Витебск, Строителей 18-4-220
Пользователь №: 11 521

|
Простите, решил сюда добавить, чтобы темы не плодить. Не так давно пишу на Си. За этот период сильно изменил свои подходы. Теперь проект разбиваю на файлы, которые логически закончены. Это даёт возможность многократного использования с минимальными изменениями. Но вот столкнулся с некоторыми проблемами в таком подходе. В принципе я о них уже читал в разных темах и общие указания слышал/запомнил.
Сама проблема возникает когда п/п из другого файла используется в прерывании, которое в main описывается. Как выходить из неё - вроде понятно. 1) Вообще убрать вызовы п/п. 2) перенести п/п в файл, где пишется прерывание.
Собственно меня интересует более общая тема. Как всётаки сделать так, чтобы и овцы целы и волки сыты. То есть вопросы следующие.
1) Правильным ли является подход с выделением логически законченного блока в отдельный файл со своим хедером. И если "нет" или "не совсем", то хотелось бы услышать как поступают проффессионалы. 2) Как использовать такие "почти библиотеки", чтобы уменьшить накладные рассходы и при этом сохранить красоту написания проги. 3) Является ли выигрышным в этом смысле вариант с С++ и созданием классов/объектов.
|
|
|
|
|
Dec 9 2007, 20:22
|
дятел
    
Группа: Свой
Сообщений: 1 681
Регистрация: 13-05-06
Из: Питер
Пользователь №: 17 065

|
Цитата(SasaVitebsk @ Dec 9 2007, 22:06)  Сама проблема возникает когда п/п из другого файла используется в прерывании, которое в main описывается. Как выходить из неё - вроде понятно. 1) Вообще убрать вызовы п/п. 2) перенести п/п в файл, где пишется прерывание. 3) объявить п/п через extern в соответствующем хидере. Цитата 1) Правильным ли является подход с выделением логически законченного блока в отдельный файл со своим хедером. И если "нет" или "не совсем" Правильным, тока для разрешения всех противоречий нужно еще иметь "главный" хидер в котором и объявляются основные настройки проекта и инклудятся все необходимые хидеры, ИМХО.
|
|
|
|
|
Dec 9 2007, 21:56
|

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

|
Цитата(SasaVitebsk @ Dec 9 2007, 21:06)  п/п из другого файла используется в прерывании, которое в main описывается. Это как? Обычно обработчик прерывания описываю не в main, а в том же файле, что и остальные п/п соответстующего модуля. А зачем обработчик уносить в тот же файл, где и main? Или это архитектера вроде pic, когда одно прервание на все случаи? Плюсы помогут, ибо в них можно в заголовочном файле описать обработчик как inline.
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
|
Dec 10 2007, 07:21
|
Местный
  
Группа: Участник*
Сообщений: 418
Регистрация: 20-08-07
Пользователь №: 29 930

|
Цитата(Непомнящий Евгений @ Dec 10 2007, 09:26)  Просветите плиз - что такое "п/п"? Видимо подпрограмма.... Она же - функция... Немного не С-шная терминология, но суть практически та же... В Фортране к примеру - были функции, кторые возврвщали значение и void-функции, которые не возвращали значение или что почти то же самое обычно - возвращали пустое значение... Еще в некоторых других языках использовалась такая терминология.
Сообщение отредактировал Николай Z - Dec 10 2007, 07:24
|
|
|
|
|
Dec 10 2007, 07:34
|
Знающий
   
Группа: Свой
Сообщений: 771
Регистрация: 16-07-07
Из: Волгодонск
Пользователь №: 29 153

|
Что такое подпрограмма - я в курсе  Просто не сообразил, что п\п - это ее сокращение. Спасибо. Цитата(Сергей Борщ @ Dec 10 2007, 00:56)  Это как? Обычно обработчик прерывания описываю не в main, а в том же файле, что и остальные п/п соответстующего модуля. А зачем обработчик уносить в тот же файл, где и main? Ну например, чтобы не зависеть от конкретного прерывания. Тогда в модуле (а точнее в хидере) я пишу inline-функцию, а само прерывание делаю в основной программе, дергая из его обработчика эту функцию. Вместо inline-функции в С можно использовать макрос.
|
|
|
|
|
Dec 11 2007, 06:17
|
Бывалый
    
Группа: Свой
Сообщений: 1 584
Регистрация: 7-08-07
Пользователь №: 29 615

|
Цитата(Сергей Борщ @ Dec 10 2007, 00:56)  А зачем обработчик уносить в тот же файл, где и main? Это дело вкуса. Я вижу это так. Если железно ясно, что - используется прерывание - и это универсальное решение, то прерывание имеет смысл иметь в модуле, где осуществляется реализация. Если : - хочется иметь main как диспетчер, в котором видна вся логика функционирования - main небольшой по размеру - возможны изменения (без прерывания или с прерыванием), то лучше обработчик прерывания в main. В дальнейшем, когда все устаканится или размер main станет неприличным по размеру, можно его перенести.
|
|
|
|
|
Dec 11 2007, 09:51
|

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

|
Цитата(alexander55 @ Dec 11 2007, 08:17)  Если : - хочется иметь main как диспетчер, в котором видна вся логика функционирования Если это ARM, то в main достаточно заносить адреса обработчиков (объявленных как extern) в нужные регистры контроллера прерываний. Тела самих обработчиков в main() не нужны. Если это проц без контроллера, то у него скорее всего вектора жестко распределены за периферией, и выбор вектора просто однозначен - в main нечего диспетчерить. Если хочется иметь выбор из нескольких одинаковых периферийных модулей (UART, например), то можно попробовать так: Код в Hardware.h указываем #define RS232_MODULE USART1 #define RS485_MODULE USART0
в RS232.c: #pragma RS232_MODULE##_UDRE_vect interrupt void RS232_transmit_handler() {
} //опять main.c не у дел Наконец, если используются плюсы и обработчик - метод класса, то код этого метода с атрибутом inline размещается в заголовочном файле, а в main.cpp пишется "обертка": Код __irq void RS232_Handler() { RS232.Handler(); } или #pragma USART1_UDRE_vect interrupt void RS232_transmit_handler() { RS232.Handler(); } при этом обработчик делается private, а функция-обертка объявляется другом класса. Но при желании эти обертки уносятся в файл соответствующего модуля и можно снова применить первый вариант. Цитата(alexander55 @ Dec 11 2007, 08:17)  В дальнейшем, когда все устаканится или размер main станет неприличным по размеру, можно его перенести. Мне кажется, что всякие методики проектирования создают именно для того, чтобы по возможности исключить такую непроизводительную работу. Хотя, конечно, идеала не существует и процесс был и будет итеративным. Но количество итераций можно попытаться уменьшить.
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
|
Dec 11 2007, 10:29
|
Бывалый
    
Группа: Свой
Сообщений: 1 584
Регистрация: 7-08-07
Пользователь №: 29 615

|
Цитата(Сергей Борщ @ Dec 11 2007, 12:51)  Наконец, если используются плюсы и обработчик - метод класса, то код этого метода с атрибутом inline размещается в заголовочном файле, а в main.cpp пишется "обертка": Код __irq void RS232_Handler() { RS232.Handler(); } или #pragma USART1_UDRE_vect interrupt void RS232_transmit_handler() { RS232.Handler(); } Я это и имел ввиду. Цитата(Сергей Борщ @ Dec 11 2007, 12:51)  в main нечего диспетчерить. Это не совсем диспетчеризация и даже может совсем не диспетчерезация. Приятно сразу понять логику работы всей системы глядя на один модуль. Детали находятся за рамками и если требуются, то понятно, где это можно посмотреть (глядя на модуль). Второй смысл - это скажем так "просмотр синхронизации потоков и распределенных приложений". Во выдал.
|
|
|
|
|
Dec 11 2007, 10:58
|
Гуру
     
Группа: Свой
Сообщений: 2 712
Регистрация: 28-11-05
Из: Беларусь, Витебск, Строителей 18-4-220
Пользователь №: 11 521

|
Я, собственно так и работаю. И думал так все.  В смысле "main" главная или основная. В main.h описываю основные переменные проекта. Например частоту проца и прочее. Этот хидер включаю во все хидера составных частей проекта (В данном последнем это Kls3x4.h, timerhw.h, lcd44780.h, wake.h). В каждом из составляющих хидеров определяются внутренние константы и объявления данного модуля и прототипы функций. В самой main.c описывается вся программа опирающаяся на составные части. Прерывание описывается в main.c Прерывание - метки времени. То есть там целый набор.  в том числе вызовы опроса клавиатуры. Вот и получается - засунуть её в Kls3x4.с некрасиво (там логически законченная библиотека) а при применении в main - есть накладные. Собственно само по себе, естественно, это проблему не вызывает. Данное прерывание обрабатывается раз в 10мс и, честно говоря мне до лампочки что там весь контекст сохраняется. Я просто на будущее. Чтобы оценить кто как работает с оформлением и структурой программы. Ведь не маловажная причина перехода на Си - это читаемость программы её структурированность и возможность неоднократного применения модулей. По крайней мере я так рассматриваю. А опыта пока не хватает. Это я вижу хотябы по тому, что у меня от проекта к проекту пока меняются некоторые моменты оформления. Думаю конечно скоро всё устаканится. Спасибо за советы.
|
|
|
|
|
Dec 11 2007, 13:07
|

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

|
Цитата(alexander55 @ Dec 11 2007, 12:29)  Приятно сразу понять логику работы всей системы глядя на один модуль. У меня в main.c обычно находятся собственно main() и __low_level_init(). Глядя на main() можно попытаться понять логику работы всей системы, правда есть одно исключение  : Код int main() { OS::Run(); } Цитата(alexander55 @ Dec 11 2007, 12:29)  Детали находятся за рамками и если требуются, то понятно, где это можно посмотреть (глядя на модуль). Так RS232_transmit_handler() - это и есть детали. А логика описывается getchar(), putchar() и hasinput(). Мне так кажется... Цитата(SasaVitebsk @ Dec 11 2007, 12:58)  Прерывание - метки времени. То есть там целый набор.  в том числе вызовы опроса клавиатуры. В таком случае наименьшие накладные, наверное, можно получить если "набор" описать в заголовочных файлах как static inline функции.
--------------------
На любой вопрос даю любой ответ"Write code that is guaranteed to work, not code that doesn’t seem to break" ( C++ FAQ)
|
|
|
|
|
Dec 11 2007, 13:39
|
Бывалый
    
Группа: Свой
Сообщений: 1 584
Регистрация: 7-08-07
Пользователь №: 29 615

|
Цитата(Сергей Борщ @ Dec 11 2007, 16:07)  У меня в main.c обычно находятся собственно main() и __low_level_init(). Глядя на main() можно попытаться понять логику работы всей системы, правда есть одно исключение  : Код int main() { OS::Run(); } Это логичный и устоявшийся модуль. Цитата(Сергей Борщ @ Dec 11 2007, 16:07)  Так RS232_transmit_handler() - это и есть детали. А логика описывается getchar(), putchar() и hasinput(). Мне так кажется... В данном случае так. Цитата(Сергей Борщ @ Dec 11 2007, 16:07)  В таком случае наименьшие накладные, наверное, можно получить если "набор" описать в заголовочных файлах как static inline функции. Или включить максимальную оптимизацию по требуемому параметру.
|
|
|
|
|
Apr 7 2008, 13:32
|
Местный
  
Группа: Свой
Сообщений: 335
Регистрация: 17-06-04
Из: Москва
Пользователь №: 35

|
Подниму тему, чтоб не плодить... Объявляю внешнюю функцию: Код extern void LCD_INIT ( void ); В main делаю вызов Код LCD_INIT(); Не работает. Вставляю в main тело функции - все работает. IAR не ругается и не предупреждает. Файлы прикрепил. Помогите понять, плиз
--------------------
Всегда не хватает времени, чтобы выполнить работу как надо, но на то, чтобы ее переделать, время находится. (Закон Мескимена.)
|
|
|
|
|
Apr 7 2008, 13:45
|
Знающий
   
Группа: Свой
Сообщений: 526
Регистрация: 24-08-07
Из: Беларусь, Минск
Пользователь №: 30 045

|
Цитата(Panych @ Apr 7 2008, 16:32)  Подниму тему, чтоб не плодить... Объявляю внешнюю функцию: Код extern void LCD_INIT ( void ); В main делаю вызов Код LCD_INIT(); Не работает. Вставляю в main тело функции - все работает. IAR не ругается и не предупреждает. В пятницу текст выводился, все вроде было именно так организовано - а в понедельник... Файлы прикрепил. Помогите понять, плиз  Судя по вашему проекту, функция LCD_INIT() у Вас не внешняя а очень даже локальная. Если уж решили разбивать проект на модули - доводите дело до конца, а не сводите все "модули" в один через #include. С минимальной правкой того что есть, можно перенести прототип функции LCD_INIT() до тела самой функции. Код void LCD_INIT ( void ); void LCD_INIT ( void ) { ... } И ещё совет: все прототипы лучше описывать в заголовочных файлах.
|
|
|
|
|
Apr 7 2008, 14:15
|
Местный
  
Группа: Свой
Сообщений: 335
Регистрация: 17-06-04
Из: Москва
Пользователь №: 35

|
gottyспасибо, сделал функцию внешней, IAR ругнулся на то, что в функции было объявлено Код extern void DELAYMKS (); extern void DELAYMS (); а не Код extern void DELAYMKS ( unsigned long mks ); extern void DELAYMS ( unsigned long ms ); исправил и все вроде заработало...
--------------------
Всегда не хватает времени, чтобы выполнить работу как надо, но на то, чтобы ее переделать, время находится. (Закон Мескимена.)
|
|
|
|
|
Apr 8 2008, 12:04
|
Знающий
   
Группа: Свой
Сообщений: 589
Регистрация: 24-04-05
Пользователь №: 4 447

|
Цитата(gotty @ Apr 7 2008, 16:45)  И ещё совет: все прототипы лучше описывать в заголовочных файлах. ... и чтобы исключить многократные включения заголовочных файлов в начале каждого хидера применять "заглушку" типа: Код #ifndef XXX_H #define XXX_H
..................
#endif
|
|
|
|
|
Apr 8 2008, 12:11
|

Беспросветный оптимист
     
Группа: Свой
Сообщений: 4 640
Регистрация: 26-12-07
Из: Н.Новгород
Пользователь №: 33 646

|
Цитата(Panych @ Apr 7 2008, 17:15)  gottyспасибо, сделал функцию внешней, IAR ругнулся на то, что в функции было объявлено Код extern void DELAYMKS (); extern void DELAYMS (); очень правильно ругнулся, ибо () означает void, а вы туда переменные суёте...
--------------------
Программирование делится на системное и бессистемное. ©Моё :) — а для кого-то БГ — это Bill Gilbert =)
|
|
|
|
|
Apr 9 2008, 15:51
|
Знающий
   
Группа: Свой
Сообщений: 771
Регистрация: 16-07-07
Из: Волгодонск
Пользователь №: 29 153

|
Цитата(ivainc1789 @ Apr 9 2008, 19:09)  Тема о многостраничных файлах, задам и свой махонький вопрос. А как описать внешние метки? ... #include <setjmp.h> Описание можно посмотреть например здесь http://www.freetype.org/david/reliable-c.html#annex-AНо можно вопрос - а зачем вам это надо???
|
|
|
|
|
Apr 9 2008, 21:55
|
Гуру
     
Группа: Свой
Сообщений: 10 920
Регистрация: 5-04-05
Пользователь №: 3 882

|
Цитата(ivainc1789 @ Apr 9 2008, 21:09)  Тема о многостраничных файлах, задам и свой махонький вопрос. А как описать внешние метки? Например, в main.c у меня описана метка Sleep. В другом файле модуля есть необходимость в безусловном переходе на Sleep типа goto Sleep. Сходу в документации не нашел как " ласково попросить" компилятор считать метку Sleep внешней? Конструкции типа extern label Sleep естественно не прокатывают...  Жесть!  Использование goto в программах на Си вообще считается моветоном, а чтобы еще и скакать по goto из одного файла в другой мне бы вообще в голову не пришло  Насколько я понимаю метка всегда привязана к оператору или функции, поэтому ее область видимости ограничена этой самой функцией или модулем. В самом стандарте ANSI C про goto говорится что Цитата 6.8.6.1 The goto statement Constraints 1 The identifier in a goto statement shall name a label located somewhere in the enclosing function. A goto statement shall not jump from outside the scope of an identifier having a variably modified type to inside the scope of that identifier. Т.е. стандартными средствами Си "прыгнуть" по goto, допустим, внутрь другой функции нельзя. Тем более нельзя для функции, описанной в другом модуле.
|
|
|
|
|
Apr 13 2008, 10:55
|

Местный
  
Группа: Участник
Сообщений: 355
Регистрация: 27-03-07
Из: Україна, Чуднів
Пользователь №: 26 530

|
Добрый день! Многофайловый проект, чёрт по..... Подскажите, как обойти ошибку, извиняюсь что может быть повторяюсь. Просто переопределения нигде не вижу, понимаю что чтото с включением файлов, пытался экстернить но не помогло. Прикреплю проджект на всякий случай. Спросил на телесисах, но там молчок Error[e27]: Entry "EN" in module driver_lcd ( C:\Program Files\IAR Systems\test\testlsd\Debug\Obj\driver_lcd.r90 ) redefined in module main ( C:\Program Files\IAR Systems\test\testlsd\Debug\Obj\main.r90 ) ИАР версия 4.хх. имею след код файл bitdef.h Код #ifndef BITDEF_H #define BITDEF_H #ifndef BOOL_t #define BOOL_t typedef uint8_t bool_t; #endif // BOOL_t #ifndef BitStr_t #define BitStr_t typedef volatile uint8_t * port_t; typedef struct{ port_t Port; bool_t Bit; }Port_Bit; #endif // BitStr_t #pragma inline=forced void SETDDR(const Port_Bit * pb){ *(pb->Port-1) |= 1 << pb->Bit; } #endif //BITDEF_H файл driver_lcd.c Код #ifndef driver_lcd_C #define driver_lcd_C #include "driver_lcd.h" #include "delay.h" // Команда на настройку выводов управления линиями управления ЖКИ на выход void lcd_CMND_port_out(void) { SETDDR(RW);\ SETDDR(EN);\ SETDDR(RS);\ }; #endif //driver_lcd_C файл driver_lcd.h Код #ifndef driver_lcd_H #define driver_lcd_H #include "bitdef.h" // "E" Clock Port_Bit dEN={&PORTB, 3}; const Port_Bit * EN = &dEN; void lcd_CMND_port_out(void); #endif //driver_lcd_H
Сообщение отредактировал sKWO - Apr 13 2008, 10:58
--------------------
нельзя недооценивать предсказуемость глупости
|
|
|
|
|
Apr 13 2008, 14:08
|
Знающий
   
Группа: Свой
Сообщений: 589
Регистрация: 24-04-05
Пользователь №: 4 447

|
Цитата(sKWO @ Apr 13 2008, 13:55)  файл driver_lcd.h Код #ifndef driver_lcd_H #define driver_lcd_H #include "bitdef.h" // "E" Clock Port_Bit dEN={&PORTB, 3}; <<<<<<<<!!!!!!!!!! const Port_Bit * EN = &dEN; <<<<<<<<!!!!!!!!!! #endif //driver_lcd_H У вас в driver_lcd.h вынесено определение указателя EN. И это, естественно, является причиной переопределения. Нарушено правило одного определения. Перенесите определения в driver_lcd.с, а в driver_lcd.h объявите эти переменные с модификатором extern. PS. Это же касается и других переменных. И уберите обратные слеши в определении lcd_CMND_port_out. Они там ни к чему. PS2. И Код #ifndef driver_lcd_C #define driver_lcd_C ... ... это лишнее.
|
|
|
|
|
Apr 13 2008, 18:14
|

Местный
  
Группа: Участник
Сообщений: 355
Регистрация: 27-03-07
Из: Україна, Чуднів
Пользователь №: 26 530

|
Цитата(alux @ Apr 13 2008, 17:08)  У вас в driver_lcd.h вынесено определение указателя EN. И это, естественно, является причиной переопределения. Нарушено правило одного определения. Перенесите определения в driver_lcd.с, а в driver_lcd.h объявите эти переменные с модификатором extern. Спасибо добрый чел, а то как-то грустно было вместо файла driver_lcd.h в мейне подключать файл driver_lcd.c. Так компилится без ошибок. Поэтому и было Код #ifndef driver_lcd_C #define driver_lcd_C Сам знаю что не правильно, а с меня пиво  , щас программы нету под рукой но думаю что вопрос будет закрыт
--------------------
нельзя недооценивать предсказуемость глупости
|
|
|
|
|
Apr 14 2008, 04:44
|
Знающий
   
Группа: Свой
Сообщений: 771
Регистрация: 16-07-07
Из: Волгодонск
Пользователь №: 29 153

|
Цитата(sKWO @ Apr 13 2008, 14:55)  Код typedef uint8_t bool_t; ... typedef struct{ port_t Port; bool_t Bit; }Port_Bit; ... Port_Bit dEN={&PORTB, 3}; Странно как то - eсли вы объявили тип bool_t, то зачем туда запихивать 3???? И вообще, ИМХО, очень уж у вас сложный способ устанавливать биты в порту  Может быть, конечно, ваш компилятор все это соптимизирует... И еще вопросик - зачем вам такие конструкции: Цитата Код #ifndef BOOL_t #define BOOL_t typedef uint8_t bool_t; #endif // BOOL_t У вас bool_t что, в разных h-файлах объявляется? Объявляйте только в одном, и затем включайте везде, куда надо. Код сильно упростится...
|
|
|
|
|
Apr 14 2008, 06:33
|
Знающий
   
Группа: Свой
Сообщений: 589
Регистрация: 24-04-05
Пользователь №: 4 447

|
Цитата(Непомнящий Евгений @ Apr 14 2008, 07:44)  И вообще, ИМХО, очень уж у вас сложный способ устанавливать биты в порту  Присоединяюсь. Код #ifndef BOOL_t #define BOOL_t typedef uint8_t bool_t; #endif // BOOL_t Подключите stdbool.h, и пользуйтесь bool как полноценным типом.
|
|
|
|
|
Apr 20 2008, 10:34
|
Группа: Новичок
Сообщений: 14
Регистрация: 20-04-08
Пользователь №: 36 922

|
Цитата(jorikdima @ May 6 2007, 18:20)  хидеры то подключены, а в опциях проекта пути к ним прописаны? (если они по поддиректориям распределены) У меня была подобная проблема, только в MPLab для PIC. Думал рехнусь, и путь к линкеру был прописан в опциях проекта. Все решилось, когда взял в фигурные скобки етот путь.
|
|
|
|
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0
|
|
|