Перейти к содержанию
    

Как писать на С++ при создание приложений под ARM

История моей жизни. Когда-то очень давно, когда компьютеры были большие, микроконтроллеры были 8080, а персональный компьютер был IBM PC/XT с тактовой частотой аж 4.77 MHz.... Но тем не менее фирма Borland выпустила для персоналки первый компилятор C++ V1.0. Я, имея приличный опыт писания на FORTRAN и немного на ASM (хотя наверное приличный, потому-что BOIS для XT у которой сдохла оная пззушка я таки написал), решил воспользоваться плюсами дабы написать программатор РФ-ок. Естественно с окошками, редакторами и прочим. Окошки, конечно, не GUI, а текстовые. Рядом был программист имеющий достаточно большой для тех времен опыт писания на C, и очень отговаривавший меня от C++ - типа громоздко, криво, тормозаааа..... Но я был упорным :). Сидел, грыз книгу, думал, проникался.... Кончилось все спором, кто напишет оконную библиотеку более быструю ( тогда на XT скорость прорисовки можно было вообще наблюдать невооруженным глазом :( ) компактнную и за ограниченное время.

Спор при все своей опытности коллега проиграл - уж больно все окошки на плюсы хорошо легли :). Потом правда отыгрался - полез в наглую ASM встави делать, подчищать...

Я правда в тоже туда-же. В общем библиотечка удалась и использовал я ее долго....Как и C++. Потом пришло осознание того, что Борлондячий компилятор дерьмо редкое, пришлось переходить на другие, пришлось использовать С, поскольку времена были стародавние и плюсовых компиляторов было немного :(. Как-то получалось так, что я начал писать в даже на С++ в посконном С стиле, дабы портировать можно было-бы без больших переделок. В общем, я сейчас на плюсах пишу редко, но я не представляю себе, как-бы я писал на C не пройдя школу С++. Там есть масса вещей и подходов которые НЕОБХОДИМО осознать, понять и использовать вне зависимости от языка. Еще скажу, что компиляторы (особо С++) в те времена были реально неумные и ручками на С их при ДОСТАТОЧНОМ ОПЫТЕ можно было таки уделать. Все не стояло на месте - на С++ компиляторы теперь грех жаловаться. Коллеги! Учите и проникайтесь идеями С++, даже если потом будете на BASIC писать.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Что-то я не слыхал, чтобы в С++ где-то резко сократились ошибки памяти. Наоборот, прибавилось ошибок. Утечки памяти - фирменная фича неуправляемого С++. В бусте напихали целую кучу умных указателей для борьбы с утечками и всё равно регулярно где-то подтекает на больших проектах.

 

Вы реально пишите глупости...

Очевидно что для Вас вопрос выбора между С и С++ это вопрос религии и только

Поэтому я бы Вам советовал быть более объктивным уж если Вы пытаетесь давать определенные оценки

 

А если у Вас был неудачный опыт с С++, ну тогда думаю сами знаете что "хорошему" танцору мешает

 

А "ошибки памяти", "Утечки памяти" и прочее прочее это "фирменная фича" неаккуратных и безграмотных "специалистов" а не "неуправляемого С++"

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Потому что разработчики компиляторов умные, а программист только-только Hello World освоил. :)

if(разработчики_компилятор != программисты)

{

мир_в_хаосе_и_я_ничего_не_понимаю_в_нем();

panic_kernel(&мой_мозг);

}

 

Дайте маленькому ребётенку острый нож, и получите тот же эффект: ребенок будет считать нож злом и говорить всем, что ножом нельзя пользоваться :rolleyes:

Вообще во всём мире С++ верно помирает, а вы наоборот в контроллеры его суете.

Да? А мужики-то не знают :rolleyes: Может будем в МК вставлять .NET и Java сразу?

На С++ я успел поработать и мне не понравилось. И шаблоны, и ОО, и перегрузки - основные фичи С++ - разводят страшную грязь в коде. Да, при хорошем опыте программист научается ходить по этим граблям, но новый программист в проекте будет разбираться очень долго. :)

Это все субъективно. Есть люди и с противоположным мнением. Кстати, не каждый новый программист код на чистом Си разберет.

 

 

Я бы скромно предложил закрыть тему, как воинствующую)))

 

хотя наверное приличный, потому-что BOIS для XT у которой сдохла оная пззушка я таки написал

Мое Вам уважение! Я дальше самописного бутлоадера (на нулевой дорожки дискеты) и попытки написать примитивную миниОС в защищенном режиме IA32 не ушел. Но это было в далеком 2004 году, сейчас я немного взрослее стал))) Хотя, надо признаться, опыт был неплохой получен. Также делал окна в текстовом режиме, кнопочки и т.д. и т.п. Все на ассемблере...

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Кстати, прорабатывается новая версия стандарта С++ - это С++ 0x, главной задачей которого является развитие языка С++. Вот тут впринципе не плохо описано, можно пичитать: тыц

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Кстати, прорабатывается новая версия стандарта С++ - это С++ 0x, главной задачей которого является развитие языка С++. Вот тут впринципе не плохо описано, можно пичитать: тыц

Ну, наконец-то!

В стандартном C++ для перебора элементов коллекции требуется масса кода. В некоторых языках, например, в C#, есть средства, предоставляющие «foreach»-инструкцию, которая автоматически перебирает элементы коллекции от начала до конца. C++0x вводит подобное средство. Инструкция for позволит проще осуществлять перебор коллекции элементов:

int my_array[5] = {1, 2, 3, 4, 5};
for(int &x : my_array)
{
  x *= 2;
}

Эта форма for, называемая в английском языке «range-based for», посетит каждый элемент коллекции. Это будет применимо к C-массивам, спискам инициализаторов и любым другим типам, для которых определены функции begin() и end(), возвращающие итераторы. Все контейнеры стандартной библиотеки, имеющие пару begin/end, будут работать с for-инструкцией по коллекции.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Я дальше ....

Уж больно у меня стимул был велик - в 80-е годы заполучить ПЕРСОНАЛЬНЫЙ!! КОМПЬЮТЕР! дома!!! По этой причине моей первой программой на ASM 8088 стал BIOS :). Ох, помнится, и попотел я.... А еще он мог, как с родными работать с 800K дискетами :) :).

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Пописал немного кода на С++ использованием динамического полиморфизма и шаблонов...

компиллер юзал gcc-4.6.1. всякую динамическую дрянь, rtti ессно поотключал.

Про конструкторы и деструкторы правда пришлось забыть, тк память под обьект обычно выделяется статически(читай, во время ресета контроллера),

а инициализировать его можно аж после загрузки какой-то части ОС, при чем для разных классов это может быть по разному (на пример гуи должен стартовать после драйверов итп).

Хотя завести init() вместо конструктора для каждого класса и вызвать в нужном месте для нужного обьекта не составляет особого труда.

 

Ради интереса взял кусок своей старой чисто-сишной гуи-либы под 128х64 монохромный дисплей и переписал на С++.

В сишной версии рулили всем указатели на таблицы функций(таких как draw,keypressed,...)

После переноса этого на плюсы, все было заменено на классы с виртуальными ф-ями и рулить стали указатели на обьекты.

Хохма - асм-код остался Идентичен по размеру и инструкциям, только адреса поменялись! А исходник на плюсах стал меньше и гораздо красивее.

Оказывается, я уже давно юзаю оо, но все руцями :)

 

Далее перевел на плюсы с использованием шаблонов thread-safe queue, сишная версия которого пестрела макросами для реализации поддержки разных типов данных(время на написание этих макросов ушо больше, чем бы я писал отдельные функции для каждого типа :))

Тут сгенерированный код остался Полностью Идентичен сишной версии :)

 

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

 

Еще

От множественного наследования отказался сразу. Имхо,для 256кб флеша оно не надо, да и во многих оо-языках его нету

Обработку исключений не пробовал. по идее, если ее правильно реализовать в компиляторе - производительность должна повысится, в свравнении с техникой возврата кода ошибки, неговоря уже о красоте кода( куча дефайнов с кодами ошибок, куча if,goto, путаница где вызвать printf, где нет итп). Хотя оно не избавляет от проблем выделения/освобождения ресурсов: выделили 10 ресурсов, 11й не вышло - надо освобождать все обратно, а обработчик знать не может что там выделено и сколько, особенно,если там куча вложенных функций, которые тоже что-то выделяли. в итоге городить if/goto всеравно прийдется...

Перегрузка операторов и потоков пока вроде не нужна. не нашел места применения. думалось заюзать перегрузку +-=/*% для работы с большими числами(скажем в 2048бит, нужно для таких алгоритмов, как RSA,DSA,...), но очередность там не однозначная получается,многое зависит от компилятора(особенно он любит пихат оператор = там,где не надо), в итоге пожирается память, вызывается довольно тяжелые функции лишний раз, а красота кода не сильно улучшается.

на пример а=(b*c)%m

у меня:

mul(t,b,c);

mod(a,t,m);

а иногда, если размер a больше чем размер b+c

mul(a,b,c);

mod(a,a,m);

 

у gcc

mul(t,b,c);

mod(t1,t,m);

equ(a,t1,m);

а иногда еще хуже, когда несколько вложенных скобок...

 

по поводу C++11, имхо для embedded ничего полезного. кроме как инициализация отдельных полей структур по имени(уже давно есть в C, в C++ никак не перекочует). Еще в c++ нету VLA (variable length array) - создние в стеке массива неконстантной длины.в c99 это есть и прекрасно работает(при аккуратном использовании) - экономит память.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

  1. Почему вы ставите знак равенства между С++ и ООП?

Потому что С++ - это язык ООП. Всё остальное, что в нем есть - это (ненужные) навороты, главная его особенность - объектная ориентированность.

 

Что-то я не слыхал, чтобы в С++ где-то резко сократились ошибки памяти. Наоборот, прибавилось ошибок. Утечки памяти - фирменная фича неуправляемого С++.

О как! Сильно сказано. Только вот как раз ООП и придумано для того, чтобы можно было не сократить, а полностью ликвидировать утечки памяти - просто нужно тщательно продумать структуру программы и распределение объектов. И выделять память в конструкторе объекта, а удалять в деструкторе. Тогда их не будте никогда. Разве что у тех программистов, которые так и не поняли смысл объектно-ориентированного программирования.

 

С++ позволяет множественное наследование, вот и упомянул. Просто перечисление объективных недостатков языка.

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

 

Адресная арифметика - это также и чисто микроконтроллерная область. И хоть вы на чём пишите, залезать в неё придётся.

Зачем? Вот убей не пойму, почему когда пишут прикладную программу для обычного компьютера, она не требуется, а в микроконтроллерах непременно нужна?

 

А неаккуратная работа с памятью более вероятна как раз в С++, потому что там куда чаще встречается динамическое размещение объектов.

Динамическое размещение объектов встречается везде, но в C нет средств для аккуратной работы с ними, а в C++ есть.

 

На С++ голову уж слишком сильно нужно включать.

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

Изменено пользователем 777777

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Про конструкторы и деструкторы правда пришлось забыть, тк память под обьект обычно выделяется статически(читай, во время ресета контроллера),

а инициализировать его можно аж после загрузки какой-то части ОС, при чем для разных классов это может быть по разному (на пример гуи должен стартовать после драйверов итп).

Хотя завести init() вместо конструктора для каждого класса и вызвать в нужном месте для нужного обьекта не составляет особого труда.

Чем же так конструкторы не угодили? Это просто удобно и безопасно - автоматическая инициализация при создании объектов. А с отдельным init'ом обычное дело забыть сунуть туда инициализатор (люди постоянно ошибаются :) ).

 

От множественного наследования отказался сразу. Имхо,для 256кб флеша оно не надо,

Необходимость в множественном наследовании зависит не от объёмов программы, а от её концепции и задач. Например, когда надо объединить функционал двух объектов, удобно отнаследоваться от них обоих. Кода это не добавляет.

 

Обработку исключений не пробовал. по идее, если ее правильно реализовать в компиляторе - производительность должна повысится, в свравнении с техникой возврата кода ошибки, неговоря уже о красоте кода( куча дефайнов с кодами ошибок, куча if,goto, путаница где вызвать printf, где нет итп).

Не, обработка исключений точно не катит в embedded - это самый тяжёлый механизм С++. Там при выбросе исключения происходит так называемая "раскрутка стека", это длительный процесс со слабопредсказуемыми времянками и значительным потреблением ресурсов - процессорного времени.

 

по поводу C++11, имхо для embedded ничего полезного.

Полезная фишка там есть - rvalue reference (обозначается оператором &&). Позволяет избежать ненужного копирования в ряде случаев.

 

Потому что С++ - это язык ООП. Всё остальное, что в нем есть - это (ненужные) навороты, главная его особенность - объектная ориентированность.

Правда? Т.е. сами по себе классы нафиг не нужны? И перегрузка имён функций/операторов не нужна? И возможность объявлять объекты в любом месте программы не нужна? И наследование (без виртуальных функций) не нужно? И шаблоны не нужны?

 

Да будет вам известно, что исходно С++ появился именно как объектный язык, и ОО составляющая была в него добавлена значительно (годы) позже.

 

С++ никогда не был чистым ОО языком - ООП в нём - это лишь один (и не очень обширный) из его аспектов.

 

Либо (если не согласны в вышеперечисленным) вы не вполне понимаете, что такое ООП.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Про конструкторы и деструкторы правда пришлось забыть, тк память под обьект обычно выделяется статически(читай, во время ресета контроллера)

 

При embedded программировании надо, для начала, понять, как устроен конкретный компилятор и его startup файл, где и в каком порядке что должно инициализироваться. Тогда вы будете управлять процессом проектирования, а не идти на поводу "левых" demo проектов.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Чем же так конструкторы не угодили? Это просто удобно и безопасно - автоматическая инициализация при создании объектов. А с отдельным init'ом обычное дело забыть сунуть туда инициализатор (люди постоянно ошибаются ).

Да, это удобно и на PC я их использую во всю. а вот конкретно на моей платформе с ними проблеммы...

Кога обьект размещен в статической памяти(у меня это только bss забиваемый при старте нулями), gcc создает:

1. константную таблицу init_array с указателями на функции(те самые конструкторы/инициализаторы указателей на vtables). Ессно, если в классе есть виртуальные функции, то GCC по любом создаст конструктор либо дополнит существующий кодом инициализации указателя на vtable

2. саму констнтую vtable

пример

class A{
public:
    virtual int a()=0;
};

class B :public A{
public:
    B(){z=0x12345;};
    int a(){return z++;};
private:
    int z;
};

B bcc;

Disassembly of section .text:
00008004 <_GLOBAL__sub_I_bcc>:
    8004:    f240 034c     movw    r3, #76; 0x4c
    8008:    f242 3245     movw    r2, #9029; 0x2345
    800c:    4903          ldr    r1, [pc, #12]; (801c <_GLOBAL__sub_I_bcc+0x18>)
    800e:    f2c0 0301     movt    r3, #1
    8012:    f2c0 0201     movt    r2, #1
    8016:    e883 0006     stmia.w    r3, {r1, r2}
    801a:    4770          bx    lr
    801c:    00008038     andeq    r8, r0, r8, lsr r0
00008020 <_ZN1B1aEv>:
    8020:    6843          ldr    r3, [r0, #4]
    8022:    1c5a          adds    r2, r3, #1
    8024:    6042          str    r2, [r0, #4]
    8026:    4618          mov    r0, r3
    8028:    4770          bx    lr
    802a:    bf00          nop
Disassembly of section .rodata:
00008030 <_ZTV1B>:
    8038:    00008021     andeq    r8, r0, r1, lsr #32
Disassembly of section .init_array:
00010048 <__data_start-0x4>:
   10048:    00008005     andeq    r8, r0, r5

В итоге, функции по указателям в .init_array должны буть когда-то вызваны до использования обьекта.

НО. Некоторые обьекты нужно проинициализировать сразу после ресета(до старта ОС).

Некоторые после старта ОС но до старта конкретных драйверов периферии. почему - потому что в их конструкторах есть вызовы функций ОС, если их вызвать до старта ОС - будет креш.

А некоторые после старта всех драйверов и до выполнения первого юзерского треда(приложения). Аналогично - там могут быть обращения к дровам, выделение системных ресурсов(создание тредов, мютексов итд).

В итоге как мне знать когда вызвать ту или иную функцию(конструктор) из init_array ?

Потому я пошел по простому пути - отказался от конструкторов; обрабатываю init_array(туда компиллер кидает код инициализации указателей на vtable) еще до старта системы, что есть безопасно, тк в конструкторах нету системозависимого кода; А уже инициализацию обьектов провожу явно в нужном месте в нужное время посредством вызова init();

Думаю понятно изложил ;)

 

Необходимость в множественном наследовании зависит не от объёмов программы, а от её концепции и задач. Например, когда надо объединить функционал двух объектов, удобно отнаследоваться от них обоих. Кода это не добавляет.

Ну мож иногда и надо, я пока применению ему не нашел. а про 256кб упомянул с намеком на относительную простоту програм с данным обьемом :)

 

Не, обработка исключений точно не катит в embedded - это самый тяжёлый механизм С++. Там при выбросе исключения происходит так называемая "раскрутка стека", это длительный процесс со слабопредсказуемыми времянками и значительным потреблением ресурсов - процессорного времени.

При выбросе исключения явно(путем возврата кода ошибки) тоже происходит раскрутка стека - мы же вернемся в самую первую функцию по цепочке из кодов ошибок: типа ошибка вылезла в недрах uartRead, выходим uartRead->SysCall->GsmModemMuxRead->GsmModemCommand->GprsWrapper->TermRead->main...

Хотя с вами соглашусь, "обработка исключений точно не катит в embedded"

 

Полезная фишка там есть - rvalue reference (обозначается оператором &&). Позволяет избежать ненужного копирования в ряде случаев.

Я привык руцями управлять перегонкой данных, ссылки использую в очень редких случаях, в основном только указатели. при паре сотен байт стека, имхо, это дело надо контролировать самому...

 

При embedded программировании надо, для начала, понять, как устроен конкретный компилятор и его startup файл

Привязыватся к компилятору - плохая идея. Мой код дожен компилится любым нормальным компилятором и работать.

Компилятор для меня - это именно КОМПИЛЯТОР, тобышь генератор кода, а не набор библиотек и прочей несовместимой хрени.

startup у меня свой равно как и ОС. Там и MPU задействован, и куча всяких вкусностей типа lock-free фишек итп, все это не совместимо ни с стандартной библиотекой ни с стартапами(которые тоже являются частью стдлиб), но зато очень удобно в ислопользовании.

 

где и в каком порядке что должно инициализироваться

как я уже писал - компилятор никак не может знать что за чем должно инициализироватся, в сучаи одновременного создания обьектов,тобышь в статической памяыти, ни я не могу быть 100% уверен,что компиллер вдруг запустит конструктор не там, где нужно.

Если обьект создается в стеке - тогда да, там можно запросто юзать конструкторы/деструкторы,да и выделение глобальных системных ресурсов для обьекта в стеке конкретного треда/прерывания не целесобразно.

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Насколько я понимаю, вот тут конструктор object будет вызван в момент первого вызова GetObject(), а не ещё до main() в порядке, определяемом линковкой файлов.

Object& GetObject()
{
    static Object object;
    return object;
}

Только радости от этого по сравнению с object.init() мало -- добавляется кусочек кода в каждом использовании, функции object при вызове GetObject().send() не проинлайнятся, ...

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Насколько я понимаю, вот тут конструктор object будет вызван в момент первого вызова GetObject(), а не ещё до main() в порядке, определяемом линковкой файлов.

Зависит от компилятора... счас проверим на GCC

Только радости от этого по сравнению с object.init() мало -- добавляется кусочек кода в каждом использовании, функции object при вызове GetObject().send() не проинлайнятся, ...

по моему init() гораздо красивее этой конструкции...возможности языка нужно применять не ради самих возможностей, а для дела(пользы). благо, Страуструп заложил в язык 2 важные вещи:

1. Он должен быть пригоден для системного программирования (те в окружении неизвестному компилятору,не зависящему одно от другого)

2. Мы не должны платить за то, чего не используем. не конструктора - не паримся с его поддержкой, нет new/delete - не паримся с поддержкой динамики итд...

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

НО. Некоторые обьекты нужно проинициализировать сразу после ресета(до старта ОС).

Некоторые после старта ОС но до старта конкретных драйверов периферии. почему - потому что в их конструкторах есть вызовы функций ОС, если их вызвать до старта ОС - будет креш.

А некоторые после старта всех драйверов и до выполнения первого юзерского треда(приложения). Аналогично - там могут быть обращения к дровам, выделение системных ресурсов(создание тредов, мютексов итд).

В итоге как мне знать когда вызвать ту или иную функцию(конструктор) из init_array ?

Ну, объекты, которые "завязаны" на аппаратуру, организовывать соответствующим образом, но все подряд-то ни к чему под одну гребёнку гладить. Большинство объектов в программе, как правило, не привязаны к аппаратуре, для них можно спокойно использовать конструкторы без ограничений.

 

Ну мож иногда и надо, я пока применению ему не нашел. а про 256кб упомянул с намеком на относительную простоту програм с данным обьемом :)

256 кБ - весьма приличный объём, если писать код своими руками (а не забить его библиотечными функциями). А по меркам embedded так даже и нифига себе. :)

 

При выбросе исключения явно(путем возврата кода ошибки) тоже происходит раскрутка стека - мы же вернемся в самую первую функцию по цепочке из кодов ошибок: типа ошибка вылезла в недрах uartRead, выходим uartRead->SysCall->GsmModemMuxRead->GsmModemCommand->GprsWrapper->TermRead->main...

Ну, некоторое сходство есть, но при исключениях там генерируется гора служебного кода, что тащит неслабый оверхед, при организации программы через возврат кода ошибки всё получается на пару порядков скромнее (хотя и не так удобно и красиво внешне).

 

Я привык руцями управлять перегонкой данных, ссылки использую в очень редких случаях, в основном только указатели. при паре сотен байт стека, имхо, это дело надо контролировать самому...

Rvalue reference - это совсем не те ссылки, которые в нынешнем С++. Это совсем другая штука. :) Обычные ссылки, кстати, тоже, имхо, зря обделяете вниманием - они в ряде случаев дают более простую семантику и более безопасный код.

 

 

 

по моему init() гораздо красивее этой конструкции...

init() обладает одним существенным недостатком - нужно не забывать корректировать его при изменениях в совсем других частях программы. В общем, это в значительной степени отказ от идеи абстракции (и инкапсуляции) данных в виде законченных объектов, т.е. в некотором роде даунгрейд в С. По мере роста сложности программ, это не добавляет радости.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Object& GetObject()

{

static Object object;

return object;

}

Хе, еще хуже. Компиллер создает в bss переменную-флаг был ли создан обьект или нет.

Далее он проверяет этот флаг. если он не установлен - вызывает функцию __cxa_guard_acquire, которая должна заблокировать этот обьект(типа открыть мютекс) и далее запускает конструктор. потом вызывает __cxa_guard_release и возвращает указатель/ссылку на обьект. :)

Ессно тело этих функций лежит на нас. И ессно это гемор, проще вызвать init(...) в нужном месте і все

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...