Важная информация

User Tag List

Страница 1 из 10 12345 ... ПоследняяПоследняя
Показано с 1 по 10 из 99

Тема: "Умная линковка" в компиляторах

  1. #1
    Veteran Аватар для Oleg N. Cher
    Регистрация
    24.08.2007
    Адрес
    Днепропетровская обл.
    Сообщений
    1,593
    Спасибо Благодарностей отдано 
    2,158
    Спасибо Благодарностей получено 
    130
    Поблагодарили
    96 сообщений
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию "Умная линковка" в компиляторах

    Если расставить точки над "i", то вся эта возня нужна лишь для того, чтобы заткнуть дыру в доброй половине компиляторов Си - они запихивают все функции откомпилированного исходника в библиотеку моноблоком, и, соответственно, при использовании хотя бы одной из них - тянутся все остальные.

    У меня тоже есть собственное решение - утилита smartlib, включенная в дистрибутив XDev. Она разрезает исходник по "линиям разреза", добавляя к каждому куску заголовок до "линии заголовка" автоматически, и не нужно вручную пихать каждую функцию в отдельный файл.

    Именно так построены все существующие на данный момент библиотеки ZXDev. Скоро сюда добавится библиотека NewSupercode (я адаптировал для ZXDev пока что только 6 подпрограмм).

    Подробнее в теме "Умная" линковка (smart linking) в ZXDev/SDCC.

  2. #1
    С любовью к вам, Yandex.Direct
    Размещение рекламы на форуме способствует его дальнейшему развитию

  3. #2
    Master
    Регистрация
    26.03.2005
    Адрес
    Ivanovo
    Сообщений
    640
    Спасибо Благодарностей отдано 
    5
    Спасибо Благодарностей получено 
    3
    Поблагодарили
    1 сообщение
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Цитата Сообщение от Oleg N. Cher Посмотреть сообщение
    Если расставить точки над "i", то вся эта возня нужна лишь для того, чтобы заткнуть дыру в доброй половине компиляторов Си - они запихивают все функции откомпилированного исходника в библиотеку моноблоком, и, соответственно, при использовании хотя бы одной из них - тянутся все остальные.
    Редкостная чепуха. Ни один из основных используемых компиляторов на не8-битках при линковании исполняемого файла со статическими библиотеками не тянет все поголовно ф-ции, если они не используются. Не надо проецировать Борландовские поделия на все остальные компиляторы.
    Как ведет себя в этом случае сдцц я не в курсе, но подозреваю что такой примитивный вариант оптимизации там осилили. Если не осилили то здравый вариант разбивать на модули с ф-циями объединенными по некоему признаку. В этом случае из статической библиотеки слинкуются только те объектники, которые содержат используемые ф-ции.

  4. #3
    Veteran Аватар для Oleg N. Cher
    Регистрация
    24.08.2007
    Адрес
    Днепропетровская обл.
    Сообщений
    1,593
    Спасибо Благодарностей отдано 
    2,158
    Спасибо Благодарностей получено 
    130
    Поблагодарили
    96 сообщений
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Цитата Сообщение от Q-Master Посмотреть сообщение
    Ни один из основных используемых компиляторов на не8-битках при линковании исполняемого файла со статическими библиотеками не тянет все поголовно ф-ции, если они не используются. Не надо проецировать Борландовские поделия на все остальные компиляторы.
    Врушки. Ещё - Microsoft Quick C, DJGPP для Windows, Tiny C (tcc) и почти все старые сишные компиляторы, с которыми я работал. Должным образом эта проблема, пожалуй, решена только в GCC (дополнительными ключами командной строки: -ffunction-sections, -fdata-section, --gc-sections) и в MS Visual C++.

    Цитата Сообщение от Q-Master Посмотреть сообщение
    Как ведет себя в этом случае сдцц я не в курсе, но подозреваю что такой примитивный вариант оптимизации там осилили.
    Нет. Я реквестил его, но Филлипа это не пробрало. Видимо, он делает библиотеки также как Sergey и не считает эту задачу насущной. Ещё я пробовал пробить данную возможность в сообщество разработчиков tcc, но и там отнеслись прохладно. Проще говоря, отказали. Почитайте, чем аргументировали.

    Так что в XDev использование утилиты smartlib - не роскошь, а злободневная потребность. Особенно с учётом того, что некоторые компиляторы уже никогда не доработают (например, Turbo C).

    Цитата Сообщение от Q-Master Посмотреть сообщение
    Если не осилили то здравый вариант разбивать на модули с ф-циями объединенными по некоему признаку. В этом случае из статической библиотеки слинкуются только те объектники, которые содержат используемые ф-ции.
    С тем, что связанные друг с другом рекурсивно функции или переменные, которые используются только совместно с некоторой функцией можно пускать одним куском, никто и не спорит.

    Вопрос в том, как именно поступить: разделить исходник библиотеки по "ф-циями объединенными по некоему признаку" на несколько кусков вручную, т.е. каждый такой кусок держать в виде отдельного файла, собирать мейком и радоваться. Либо же, как предлагаю я, иметь всю библиотеку в виде одного исходника и бить её на куски автоматически (места разделения кусков указываются программистом также вручную) в процессе сборки библиотеки. Как по мне, второй вариант значительно удобнее. Говорю как человек, сделавший большое количество библиотек для SDCC. За счёт того, что всё в рамках одного файла - проще редактировать, искать, переносить. Плюс с разделителями смотрится красиво.

  5. #4
    Vitamin C++ Аватар для Vitamin
    Регистрация
    14.01.2005
    Адрес
    Таганрог, Россия
    Сообщений
    4,254
    Спасибо Благодарностей отдано 
    9
    Спасибо Благодарностей получено 
    80
    Поблагодарили
    34 сообщений
    Mentioned
    7 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Цитата Сообщение от Oleg N. Cher Посмотреть сообщение
    Врушки. Ещё - Microsoft Quick C, DJGPP для Windows, Tiny C (tcc) и почти все старые сишные компиляторы, с которыми я работал. Должным образом эта проблема, пожалуй, решена только в GCC (дополнительными ключами командной строки: -ffunction-sections, -fdata-section, --gc-sections) и в MS Visual C++.
    Ну конечно. Если целенаправленно брать для сравнения ***** мамонта, не оборудованное требуемой фичей, то получится очень убедительный рассказ про "добрую половину компиляторов". Про icc,code warrior,watcom,clang,etc не говоря уже про С++ компиляторы, говорить и учитывать при этом не стоит- чтоб статистику не портить наверное.
    И да, djgpp какбэ базирутся на gcc- так что определись, есть в нем оптимизация или нет.

  6. #5
    Veteran
    Регистрация
    06.05.2006
    Адрес
    Ливны, Орловская обл
    Сообщений
    1,169
    Спасибо Благодарностей отдано 
    0
    Спасибо Благодарностей получено 
    0
    Поблагодарили
    0 сообщений
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    >gcc
    >есть в нем оптимизация или нет
    LTO до сих пор скорее нет, чем есть.

  7. #6
    Vitamin C++ Аватар для Vitamin
    Регистрация
    14.01.2005
    Адрес
    Таганрог, Россия
    Сообщений
    4,254
    Спасибо Благодарностей отдано 
    9
    Спасибо Благодарностей получено 
    80
    Поблагодарили
    34 сообщений
    Mentioned
    7 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Цитата Сообщение от NovaStorm Посмотреть сообщение
    LTO до сих пор скорее нет, чем есть.
    На работу связки data-section/function-section/gc-sections это как-то влияет?

  8. #7
    Master
    Регистрация
    26.03.2005
    Адрес
    Ivanovo
    Сообщений
    640
    Спасибо Благодарностей отдано 
    5
    Спасибо Благодарностей получено 
    3
    Поблагодарили
    1 сообщение
    Mentioned
    2 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Цитата Сообщение от Oleg N. Cher Посмотреть сообщение
    Вопрос в том, как именно поступить: разделить исходник библиотеки по "ф-циями объединенными по некоему признаку" на несколько кусков вручную, т.е. каждый такой кусок держать в виде отдельного файла, собирать мейком и радоваться. Либо же, как предлагаю я, иметь всю библиотеку в виде одного исходника и бить её на куски автоматически (места разделения кусков указываются программистом также вручную) в процессе сборки библиотеки. Как по мне, второй вариант значительно удобнее. Говорю как человек, сделавший большое количество библиотек для SDCC. За счёт того, что всё в рамках одного файла - проще редактировать, искать, переносить. Плюс с разделителями смотрится красиво.
    Первый вариант аутентичен для любого проекта на С/С++. В принципе он вполне аутентичен для ассемблерного кода. Исходя из этого (ну и из того что аутентичным его сделали нифига не глупые люди) это оптимальный вариант с не очень большим оверхедом.
    Второй вариант во-1 очень трудно поддерживаем, т.к. требует не статической либы с кучей скомпилированных .o файлов внутри, а какого-то адского гемороя по указыванию какие куски откуда надо выкусить и куда собрать ( с этим вполне справляется компилятор без необходимости в программисте). Во-2, такой вариант нечитаем. В-3, сборка проекта будет увеличена ровно на время разбиения мегасорца на куски и последущей компиляции этой помойки. В-4, ошибки при таком разбиении практически гарантированы и вызывают п.3 в полный рост КАЖДЫЙ раз.
    Могу предложить еще 3й вариант как компромисс между 1м и 2м:
    config.h с куче #define где указывается какие ф-ции надо компилировать из исходников, а какие нет. Вариант имеет часть недостатков 2го метода, ибо требует пересборки при каждом изменении в количестве используемых ф-ций.

    PS: Не понимаю я в чем проблема сделать выкидывание мертвого кода из собираемого файла в компиляторе...
    PPS: Не думаю что выучить make или cmake это дичайшая из проблем.

  9. #8
    Veteran Аватар для Oleg N. Cher
    Регистрация
    24.08.2007
    Адрес
    Днепропетровская обл.
    Сообщений
    1,593
    Спасибо Благодарностей отдано 
    2,158
    Спасибо Благодарностей получено 
    130
    Поблагодарили
    96 сообщений
    Mentioned
    1 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Цитата Сообщение от Vitamin Посмотреть сообщение
    Про icc,code warrior,watcom,clang,etc не говоря уже про С++ компиляторы, говорить и учитывать при этом не стоит- чтоб статистику не портить наверное.
    Наверное ты не заметил, что я говорил про старые компиляторы вообще, и про те, с которыми я работал, в частности. Перечитай пост, если от тебя это какбэ ускользнуло.

    Цитата Сообщение от Vitamin Посмотреть сообщение
    И да, djgpp какбэ базирутся на gcc- так что определись, есть в нем оптимизация или нет.
    А ещё ты наверно не обратил внимание, что я говорил про DJGPP для Windows, и я не знаю, слышал ли ты вообще про такую доработку данного компилятора от Макса Феоктистова. Я говорил не про DJGPP для DOS32, и мы можем очень продуктивно обсудить данный вопрос, только ты в этой специфической области будешь какбэ некомпетентен. Уточню, речь идёт про наличие возможности пихать в статическую библиотеку код с одного сишного файла-исходника таким образом, чтобы при линковке из библиотеки брались только используемые сущности.

    Цитата Сообщение от Vitamin Посмотреть сообщение
    На работу связки data-section/function-section/gc-sections это как-то влияет?
    Линкер Макса Феоктистова не поддерживает gc-sections.

    AOT: SDCC не оборудован обсуждаемой фичей, посему остаётся бить исходник на куски. Если кто-то пролоббирует, а разрабы SDCC реализуют, выкинем нафик smartlib и будем только рады.

    А ещё хочу заметить, что сейчас занят внедрением сматрлинковки в библиотеку Laser, и там уже получается > 120 кусков. И я бы чокнулся, если бы пришлось редактировать и переносить куски кода между 120 файлами. Это факт.

    P.S. Вот меня умиляет дикое количество здешних воинствующих сишников, не сделавших/адаптировавших ни одной библиотеки для Спека. А если я неправ, то ГДЕ ССЫЛКИ НА ЭТИ БИБЛИОТЕКИ?

    ---------- Post added at 21:19 ---------- Previous post was at 21:10 ----------

    Цитата Сообщение от Q-Master Посмотреть сообщение
    Второй вариант во-1 очень трудно поддерживаем, т.к. требует не статической либы с кучей скомпилированных .o файлов внутри, а какого-то адского гемороя по указыванию какие куски откуда надо выкусить и куда собрать ( с этим вполне справляется компилятор без необходимости в программисте).
    Всем этим "аццким гемороем" занимается одна-единственная утилита smartlib и делает всё автоматически. Программисту остаётся только указать "линии разреза" между функциями, помещаемыми в библиотеку отдельно.

    Притом она, эта утилита, очень полезна не только для SDCC, но и для tcc, DJGPP для Windows и т.д. Кстати, по методу Макса Феоктистова можно собирать сверхмаленькие EXE и DLL размером от 1 кб. Т.е. они в принципе получаются небольшими, без специальных ухищрений.

    Цитата Сообщение от Q-Master Посмотреть сообщение
    Во-2, такой вариант нечитаем.
    Во-2, вы лично не адаптировали ни одной библиотеки для SDCC и рассуждаете чисто теоретически?

    Цитата Сообщение от Q-Master Посмотреть сообщение
    В-3, сборка проекта будет увеличена ровно на время разбиения мегасорца на куски и последущей компиляции этой помойки.
    Называю нехорошим словом то, что мне не нравится, да?

    К слову, да, собирает дольше. Ну а как иначе. Но дольше не из-за разбивки, она происходит мгновенно. Дольше из-за того, что каждый кусок при сборке тянет свои хидеры, компилятору их надо заново парсить, это требует времени, в т.ч. и в случае варианта 1 от Sergey.

    Цитата Сообщение от Q-Master Посмотреть сообщение
    В-4, ошибки при таком разбиении практически гарантированы и вызывают п.3 в полный рост КАЖДЫЙ раз.
    Да чушь вы говорите. Разрабатываю библиотеки уже годами, ноль проблем. А вот пока не изобрёл такой способ - здесь некоторые товарищи бухтели про то, что "этот долбанный SDCC ещё и всё из библиотек тянет, оказывается, ай-яй-яй какой он нехороший".

    Цитата Сообщение от Q-Master Посмотреть сообщение
    Могу предложить еще 3й вариант как компромисс между 1м и 2м:
    config.h с куче #define где указывается какие ф-ции надо компилировать из исходников, а какие нет. Вариант имеет часть недостатков 2го метода, ибо требует пересборки при каждом изменении в количестве используемых ф-ций.
    Поясните.

    Цитата Сообщение от Q-Master Посмотреть сообщение
    PS: Не понимаю я в чем проблема сделать выкидывание мертвого кода из собираемого файла в компиляторе...
    Ну да, проблем вроде быть не должно, но работаем ведь с готовыми компиляторами и не всегда есть возможность повлиять на направление их развития. А вы почитали, чем аргументируют отказ внедрения этой фичи разработчики tcc? Удивительное дело, оказывается здесь всё не так однозначно, как я думал...
    Последний раз редактировалось Oleg N. Cher; 10.06.2014 в 22:21.

  10. #9
    Member
    Регистрация
    21.05.2006
    Адрес
    Canada
    Сообщений
    78
    Спасибо Благодарностей отдано 
    3
    Спасибо Благодарностей получено 
    2
    Поблагодарили
    2 сообщений
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Цитата Сообщение от Oleg N. Cher Посмотреть сообщение
    > Да чушь вы говорите. Разрабатываю библиотеки уже годами, ноль проблем. А вот пока не изобрёл такой способ - здесь некоторые товарищи бухтели про то, что "этот долбанный SDCC ещё и всё из библиотек тянет, оказывается, ай-яй-яй какой он нехороший".
    Основная проблема заключается в люди не знают, как писать библиотеки. Они используются для популярных монтажников на спектр, которые "маленькая машинка мышление" и не имеют связей возможности.

    Последовательную компоновку, безусловно, является особенностью sdasz80 и о нескольких других Z80 монтажников, как z80asm в z88dk и сборщиком в Binutils. Эти сборщики только приложить код из библиотек, которые действительно используются в программе. В z88dk мы зависим от него, потому что у нас есть более чем 64К кода и данных в библиотеке спектра, например, и если последовательную компоновку не работал, даже такой программы, как "основной () {}" будет сильному нажиму, чтобы соответствовать в 64k

    Та часть, которая ловит людей в том, что все связующие работать в единицах модулей, что соответствует отдельных файлов. Библиотека код должен быть разбит на самых маленьких функций, которые имеют смысл и те отдельные функции должны быть, содержащихся в их собственных файлов. У нас есть более 5000 в z88dk и хотя вы говорите, было бы сойти с ума, чтобы сделать это, это на самом деле лучший способ делать вещи. Одна вещь, вы найдете, как ваши библиотеки ASM расти в том, что объем памяти является проблемой. Вы хотите, чтобы ваш код библиотеки для повторного использования столько кода, сколько возможно. Вы же не хотите, например, пять различных умножения процедуры, потому что пять различных библиотеки встроены собственную умножения. Эти крошечные подпрограммы должны быть доступны как правило, и используется все, чтобы снизить конечную размер бинарного кода, и это достигается только, когда вы пишете библиотеки разбитые на мелкие кусочки. Головная боль вы получили от создания библиотеки Лазерная Basic в основном потому, что вы должны были развалится чужой монолитный каплю, и это не приятная задача, ни интересным. Но если вы пишете с нуля, это очень простой и хороший способ организовать исходный код.

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

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

    Я вижу, где вы можете сделать это для создания библиотеки с использованием языка высокого уровня. Но для императивных языков, таких как C, я все еще думаю, что это предпочтительно делать библиотеки в том же порядке, одну функцию для каждого файла. Вот пример с примитивным дискового ввода / вывода для копий в минуту: http://z88dk.cvs.sourceforge.net/vie...src/fcntl/cpm/

    Даже для абстрактных типов данных, которые эффективно объектов, это все еще хороший способ организовать код. Вот реализация векторного <char>:
    http://z88dk.cvs.sourceforge.net/vie.../b_vector/z80/

    Как это обычно бывает в C, обзорный делается с помощью искажение имени. "Asm_" показывает, что функция непосредственно вызывается из собраний, "b_vector_" делает все эти функции "методы" вектора "объекта" байт. Все "методы объекта" находятся в одной директории, так же, как все функции, реализующие + + объект C, как правило, быть в одном файле.

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

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


    == english ===

    The main problem is people do not know how to write libraries. They are used to the popular assemblers on the spectrum which are "small machine thinking" and do not have linking capability.

    Incremental linking is certainly a feature of sdasz80 and of a few other z80 assemblers like z80asm in z88dk and the assembler in binutils. These assemblers will only attach code from the libraries that are actually used in the program. In z88dk we depend on it because we have more than 64k of code and data in the spectrum library, for instance, and if incremental linking did not work, even a program like "main(){}" would be hard-pressed to fit in 64k

    The part that catches people is that all linkers work in units of modules, which corresponds to individual files. Library code must be broken into the smallest functions that make sense and those individual functions must be contained in their own files. We have over 5000 in z88dk and although you're saying it would drive you nuts to do that, it's actually the best way to do things. One thing you'll find as your asm libraries grow is that memory space is a problem. You want your library code to reuse as much code as possible. You do not want, for example, five different multiply routines because five different libraries inlined their own multiply. These tiny subroutines must be made available generally and used by everything to reduce the final binary size, and this is only achieved when you write libraries broken into small pieces. The headache you got from creating a Laser Basic library is mainly because you had to break apart someone else's monolithic blob and that's not a pleasant task nor an interesting one. But if you write from scratch it's a very simple and good way to organize source code.

    Your "smart linking" idea is going one step further but it will never gain much traction with non-object-oriented compilers simply because the method described above is better for their libraries as it enables maximum code reuse and better organization of source code.

    I can speak to that, having written thousands of library functions, that there are only disadvantages in writing library code with more than one library function per file.

    I can see where you may want to do this for making libraries using a high level language. But for imperative languages like C, I still think it is preferable to make the libraries in the same manner, one function per file. Here's an example with primitive disk i/o for cpm: http://z88dk.cvs.sourceforge.net/vie...src/fcntl/cpm/

    Even for abstract data types, which are effectively objects, it's still a good way to organize code. Here's an implementation of a vector<char>:
    http://z88dk.cvs.sourceforge.net/vie.../b_vector/z80/

    As is typical in C, scoping is done using name mangling. "asm_" indicates the function is directly called from assembly, "b_vector_" makes all these functions "methods" of the byte vector "object". All "object methods" are in one directory, just as all functions implementing a C++ object would typically be in one file.

    I think that's the best way to organize code like that. Certainly putting all those functions into one file and having the compiler split it apart has no advantage at all and is much worse from an organizational point of view.

    I think "smart linking" begins to make sense when you start looking at object oriented languages. Then it makes organizational sense to have all the functions implementing an object in one file. Then you want the compiler to go in there and pick it apart for the linker so that each object method is individually linked against. The way to do this is to have the compiler suitably mangle the names so that scoping rules can still be obeyed when individual object methods are made global for the linker.

  11. #10
    Activist Аватар для Sergey
    Регистрация
    23.12.2006
    Адрес
    Славный город Самара
    Сообщений
    473
    Спасибо Благодарностей отдано 
    89
    Спасибо Благодарностей получено 
    12
    Поблагодарили
    8 сообщений
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    По умолчанию

    Цитата Сообщение от Oleg N. Cher Посмотреть сообщение
    . Дольше из-за того, что каждый кусок при сборке тянет свои хидеры, компилятору их надо заново парсить, это требует времени, в т.ч. и в случае варианта 1 от Sergey.
    У асма, кстати, есть такая функция объявлять неизвестные имена глобальными. И этот ключик ему можно передать из sdcc.
    Код:
    sdcc -mz80 -c -Wa -g filename.c
    После этого, вроде как, не обязательно каждой функции хедер давать. Все либы так собираю, - вроде, полёт нормальный.
    С уважением,
    Gris / Red Triangle.
    _____________________________________
    ZX-EVO/TS-Labs config/NGS/HDD/SD-card
    Amiga A1200/Blizzard 1230@50/32/60GB
    Amiga A1200/Apollo 1260@66/32/60GB
    UnAmiga (C5) AGA GM7123 VideoDAC

Страница 1 из 10 12345 ... ПоследняяПоследняя

Информация о теме

Пользователи, просматривающие эту тему

Эту тему просматривают: 1 (пользователей: 0 , гостей: 1)

Похожие темы

  1. Ответов: 17
    Последнее: 26.12.2015, 23:22
  2. Ответов: 19
    Последнее: 30.09.2011, 03:08
  3. Ответов: 0
    Последнее: 15.08.2010, 14:38
  4. Ответов: 18
    Последнее: 27.08.2008, 20:27
  5. Ответов: 6
    Последнее: 20.11.2007, 11:29

Ваши права

  • Вы не можете создавать новые темы
  • Вы не можете отвечать в темах
  • Вы не можете прикреплять вложения
  • Вы не можете редактировать свои сообщения
  •