LINUX.ORG.RU

C++ — туда и обратно, или зачем нужен Boost

 , , ,


3

5

Мой предыдущий тред в Development собрал самое большое число ответов аж с сентября, то есть за последние 9 месяцев, и это лишний раз подтверждает упадок этого форума. Полагаю, кто-то должен это изменить, и поэтому мы с тобой, ЛОР, поговорим сегодня про C++.

Начиная с C++26 вместо std::function вводится пачка новых классов: std::copyable_function, std::move_only_function (доступна с C++23) и std::function_ref. Что же не так с оригинальным std::function, ты можешь спросить? А вот что:

#include <functional>
#include <print>

struct call_me {
    int x = 0;
    void operator()() {
        std::print("x was {}\n", x++);
    }
};

int main() {
    const std::function<void()> f = call_me{};
    f();
}

Несмотря на то, что переменная f объявлена константной (люблю оксюмороны!), у неё есть внутреннее состояние и оно меняется при вызовах. Компилятор это без проблем хавает.

Так вот, ковыряясь в том, зачем и кто вообще смог так насрать себе в штаны сам, я наткнулся на статью ребят, которые подводят список подобных косяков комитета C++, когда фичи живут по 10 лет и объявляются устаревшими.

Небольшой список фич, которые были придуманы, оказались не нужны/бесполезны/вредны и выкинуты:

  • Известный vector<bool>, живущий издревле в STL и про который все говорят, что его надо избегать. Частично заменяется std::bitset.
  • std::auto_ptr. Бесполезен, ломает контейнеры, выкинут на помойку в C++17.
  • Указание исключений у функции в формате throw(X, Y). Так же выкинуто в C++17.
  • std::iterator объявили устаревшим в C++17, собираются удалить в C++26.
  • std::aligned_storage и std::aligned_union добавлены в C++11, объявлены устаревшими в C++23, скоро удалят.
  • Ключевое слово register удалено в C++17, хотя всё ещё доступно в Си.
  • std::get_temporary_buffer и std::raw_storage_iterator удалены в C++20.
  • Потрясающее по эпичности фиаско с интерфейсом для сборщиков мусора. std::declare_reachable сотоварищи были добавлены в C++11. Выяснилось, что сборщики мусора для C++ писать либо никто не умеет, либо никто не хочет, поэтому в C++23 это всё удалили и сделали вид, что ничего не было.
  • Абсолютное безумие вокруг концептов, модулей и поддержки сети. Предложения одобряли, вновь отклоняли, переделывали, и по итогу теми же модулями до сих пор никто не пользуется.
  • Сопрограммы (coroutines). В том виде, в котором они есть в C++, это просто ужас. Достаточно того, что корутины требуют выделения памяти из кучи во время работы, а значит вообще не подходят для случаях, когда требуется серьёзная производительность. Например, в любом коде, требующим работы в реальном времени и не позволяющем делать системные вызовы.

Просто лютый трешак, который никто подчищать пока не собирается:

  • std::regex – лютый тормоз, рекомендуется не использовать.
  • Мертворождённый std::simd, добавленный в C++26 и уже с ходу не нужный вообще никому, потому что код с std::simd в два-три раза тормознее чем со сторонними библиотеками, просто голыми интринсиками, и даже медленнее чем просто цикл for.
  • std::async. Дескруктор ждёт завершения асинка и поэтому может залочить весь код тебе. Наконец починили в C++26, но эта штука была сломана 15 лет.
  • Отвратительно спроектированный <iostream>. Его наконец можно выкинуть и использовать std::print, но не все про это знают.
  • Абсолютно тормозные контейнеры map, set, unordered_map. Вместо первого можно использовать flat_map из C++23. Контейнеры в стандартной библиотеке Rust (BTreeMap) и другие реализации B-Tree Map их обгоняют по производительности, но тем не менее в C++ выбирают убогий дефолт.

Решения многих из этих проблем существуют в Boost и сделаны там гораздо лучше. Но в то же время возникает важный вопрос: зачем вообще нужен настолько плохо спроектированный язык, где каждое следующее поколение инженеров, работающих над ним, отменяет решения предыдущих, а код под новые стандарты часто нужно переписывать если не с нуля, то очень близко к тому? Даже процесс разработки Rust с его поехавшими клоунами в юбках на этом фоне выглядит адекватным.

В общем, всё печально, ЛОР. Такие дела.



Последнее исправление: CrX (всего исправлений: 3)
Ответ на: комментарий от mittorn

на швабре обосрались в скриптах и теперь при открытии статьи скрипт сносит весь контент и заменяет на эту строчку

Афигеть. Жаль, у меня открывается. Только что проверил.

eao197 ★★★★★
()
Ответ на: комментарий от mittorn

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

Язык усложнили, компилятор усложнили, а выигрыш - не сильно заметен.

Ну и генерация кода в compile time значительно менее интересна, нежели генерация кода в рантайме, как в лиспе, например.

Stanson ★★★★★
()
Ответ на: комментарий от eao197

копирование вместо realloc? Насколько я помню, такая проблема там имелась. Но в любом случае лучше использовать свою реализацию по возможности, особенно если не хочется внезапной DoS атаки из-за кривого STL кода

mittorn ★★★★★
()
Ответ на: комментарий от Stanson

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

mittorn ★★★★★
()
Ответ на: комментарий от mittorn

копирование вместо realloc?

realloc может переместить память на другой адрес, что похерит тебе все рекурсивные структуры, и тебе всё равно придётся проходить по каждому элементу и «чинить его». Ничем не лучше чем просто выделить новую память и скопировать/переместить данные туда.

yorshka
() автор топика
Ответ на: комментарий от mittorn

Мамка запретила? Ничего, вырастешь, съедешь от родителей, будешь делать самоссылающиеся объекты сколько влезет, в том числе на Rust.

yorshka
() автор топика
Ответ на: комментарий от mittorn

Вот же лолище, это тебе лоровские кексперты запрещают? Почему же они лучше получаются чем на сишках/плюсишках, и аж целая растовая асинхронщина на них опирается?

zurg ★★
()
Последнее исправление: zurg (всего исправлений: 1)
Ответ на: комментарий от zurg

В его оправдание, пиннинг в Rust – та ещё залупа. Как впрочем и растовый async.

yorshka
() автор топика
Ответ на: комментарий от yorshka

Ничем не лучше чем просто выделить новую память

Правильное использование realloc (комментарий)

realloc может переместить память на другой адрес,

Правильное использование realloc (комментарий)

r--r--r--
()
Ответ на: комментарий от r--r--r--

Что сказать-то хотел? Что ядро может те же физические страницы переиспользовать? Ну да, может. Адреса всё равно рискуют похериться. Если бы в C++ был std::pin как в Rust, можно было бы по этому признаку решать, делать realloc() или нет. А так увы и ах.

yorshka
() автор топика
Ответ на: комментарий от yorshka

Когда речь идёт о высокопроизводительных и высоконагруженных решениях, делать больше нечего кроме как копировать байтики. случай внутренних ссылок это лишь малая доля проблем которая может возникнуть при росте std::vector, и пожалуй, наиболее редкая. Самая частая проблема это внешние ссылки, и отказ от realloc никак её не решает. Зато своя реализация вектора с realloc позволит:
1. Запомнить старый указатель и узнать, произошло ли перемещение. Старый указатель в любом случае надо запоминать, т.к при нехватке памяти realloc вернёт 0, но не освободит память
2. Обработать ошибку нехватки памяти
3. Исправить внутренние ссылки в случае необходимости, при этом не делая лишнего копирования (копирование делается один раз если это необходимо). Да, для этого объекты должны будут реализовать такое исправление явно. Но это намного лучше - знать что делаешть, а не полагаться на то что какой-то волшебный STL за тебя всё сделатет (при этом пару раз кинет исключение, которое ты конечно же забудешь отловить, сожрёт кучу CPU циклов и раздует бинарь в несколько раз)

mittorn ★★★★★
()
Ответ на: комментарий от yorshka

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

mittorn ★★★★★
()
Ответ на: комментарий от mittorn

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

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

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

Stanson ★★★★★
()
Ответ на: комментарий от mittorn

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

Как тут уже пояснили, STL не предназначен для таких случаев. Вопрос для чего же именно предназначен STL остаётся открытым. Но допустим.

  1. Запомнить старый указатель и узнать, произошло ли перемещение. Старый указатель в любом случае надо запоминать, т.к при нехватке памяти realloc вернёт 0, но не освободит память

  2. Обработать ошибку нехватки памяти

К сожалению, в современном лялексе с включенным overcommit этой ошибки не возникает в том виде, о котором ты пишешь. Она возникает в виде SIGKILL от OOM killer, когда ты пытаешься реально использовать память. Можешь сам проверить, malloc на терабайт вернёт тебе указатель.

Опять же, лялекс – достаточно дерьмово спроектированная система, но с этим приходится жить.

Исправить внутренние ссылки в случае необходимости, при этом не делая лишнего копирования (копирование делается один раз если это необходимо). Да, для этого объекты должны будут реализовать такое исправление явно.

А чем это лучше копирования-то? И так и так у тебя O(n) вместо O(1) при добавлении элемента с ресайзом вектора будет. Другими словами, что так говно, что этак. Для общего случая нынешнего поведения std::vector вполне достаточно, особенно с учётом что он вызывает конструктор перемещения, если это возможно, и копирования (кроме самого объекта) может и не быть.

yorshka
() автор топика
Ответ на: комментарий от mittorn

копирование вместо realloc? Насколько я помню, такая проблема там имелась. Но в любом случае лучше использовать свою реализацию по возможности, особенно если не хочется внезапной DoS атаки из-за кривого STL кода

Из коробки у нас поддерживаются классы из STL (типа std::vector, std::string). Но можно сделать адаптации к другим типам контейнеров. Например, для folly::fbvector (который в случае использования std::allocator как раз использует malloc/realloc вместо собственно std::allocator-а) будет что-то вроде:

template< typename T, typename... Args >
struct result_value_wrapper< folly::fbvector< T, Args... > >
{
	using result_type = folly::fbvector< T, Args... >;
	using value_type = typename result_type::value_type;
	using wrapped_type = result_type;

	static void
	as_result( wrapped_type & to, result_type && what )
	{
		to = std::move(what);
	}

	static void
	to_container( wrapped_type & to, value_type && what )
	{
		to.push_back( std::move(what) );
	}

	[[nodiscard]]
	static result_type &&
	unwrap_value( wrapped_type & v )
	{
		return std::move(v);
	}
};

И тогда код будет выглядеть как produce<folly::fbvector> вместо produce<std::vector>.

Однако, если ориентироваться не на абстрактный std::allocator который применяет хз какую политику распределения памяти, а на что-то продвинутое (вроде mimalloc-а или tcmalloc-а), где используются пулы блоков фиксированного размера (т.е. блоки до 16 байт выделяются из пула блоков по 16 байт, блоки до 32 байт из пула блоков по 32 байта и т.д.), то более критичным станет не realloc, а growth factor для контейнера. Т.е. если у вас был вектор, который умещался в 32 байта, а затем вы хотите увеличить его объем вдвое, то скорее всего вам выдадут новый блок в 64 байта из другого пула. А когда с 64 будете увеличивать до 128 – то новый блок из совсем другого пула. И realloc здесь может помогать разве что пока у вас в векторе 1-2-3 значения совсем маленького размера (по 1-2 байта на каждое).

PS. Почитаешь экспертов с LOR-а и еще больше убеждаешься во мнении, что кроме комитета, которые не несет ответственности за то, что делает, еще одной проблемой C++ является большое количество знающих единственно правильный способ программирования на C++, а все остальное считающих ересью.

eao197 ★★★★★
()
Ответ на: комментарий от Stanson

Покажи мне программу которую принципиально невозможно написать на c++98, а только на с++23.

Написать можно, но на 23 ее можно потом еще и прочитать.

ant1
()
Ответ на: комментарий от ant1

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

Ну и вопрос о разделение run- и compile time никуда не делся. Если в 98 ещё понятно где что, то в 23 уже не разберёшься сходу, если код со всеми этими новыми фичами написан. Последствия налицо, в общем-то. Блоатварь шагает по планете.

Stanson ★★★★★
()
Ответ на: комментарий от Stanson

У тебя горячка там чтоли? C++ в девяностых-нулевых по праву считался write-only языком. Эта его слава, кстати, тянется за ним до сих пор. Но 23 это другой язык уже, мы на нем стали более лучше одеваться программировать.

ant1
()
Ответ на: комментарий от ant1

C++ в девяностых-нулевых по праву считался write-only языком.

Что за чепуху я сейчас прочитал? Какая запятая из С++98 может вызывать непреодолимые трудности в прочтении принципиально отличные от таковых в си?

r--r--r--
()
Ответ на: комментарий от ant1

Тем не менее, почти всё юзабельное написано на цепепе именно в девяностых-нулевых. Даже единственную как-то взлетевшую до массового использования ОС на цепепе именно тогда написали.

Что сейчас написано на 23 аналогичного уровня?

Stanson ★★★★★
()
Ответ на: комментарий от Stanson

Что сейчас написано на 23 аналогичного уровня?

Можно узнать, а где С++23 реализован на достаточном уровне?

Тут пока C++20 до нормальной поддержки во всей большой тройке довести не могут :(((

eao197 ★★★★★
()
Ответ на: комментарий от ant1

У тебя горячка там чтоли? C++ в девяностых-нулевых по праву считался write-only языком.

C++ в девяностых – это, фактически, Си-с-классами. Значит ли это, что сишечка – write-only язык?

Так-то можешь сходить почитать исходники движка Doom 3, если хочешь увидеть хорошо написанный проект на вон том самом C++ из конца 90х-начала нулевых. Все бы плюсисты так писали как Кармак!

yorshka
() автор топика
Ответ на: комментарий от yorshka

Опять же, лялекс – достаточно дерьмово спроектированная система, но с этим приходится жить.

Кто-то, похоже, не знает, зачем нужен overcommit, что причины его существования появились задолго до линукса, и почему любая Unix-подобная система по факту обязана его реализовывать :D

shdown ★★
()
Последнее исправление: shdown (всего исправлений: 1)
Ответ на: комментарий от shdown

Опять же, лялекс – достаточно дерьмово спроектированная система, но с этим приходится жить.

Кто-то, похоже, не знает, зачем нужен overcommit и почему любая Unix-подобная система по факту обязана его реализовывать :D

Ты про fork()? Ну, да, некоторая часть убожества досталась лялексу в наследство. Unix вообще изначально не отличался хорошим дизайном. Так, система для убогоньких недокалькуляторов из 70х, лишь по банальному недоразумению и из-за огромной корпоративной жадности ставшая популярной. А если учесть, что fork() чаще всего используется в связке fork+exec, то в принципе этот костыль можно смело выкинуть и заменить на posix_spawn().

Вообще, надо будет сделать отдельный тред тут про всё убожество unix и его API, из-за которых мы тут страдаем.

yorshka
() автор топика
Ответ на: комментарий от yorshka

А ещё можно придумать новое ABI, где указатели передаются вместе с границами! Да и вообще новую ISA чисто для Go, а то там ооверхед на вызов сишных функций огромный!

Если бы этот твой лялекс ломал обратную совместимость, он бы не стал популярным. А большое количество старого софта всё ещё делает fork() + exec*() по-старинке.

shdown ★★
()
Последнее исправление: shdown (всего исправлений: 2)
Ответ на: комментарий от shdown

А ещё можно придумать новое ABI, где указатели передаются вместе с границами!

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

Если бы этот твой лялекс ломал обратную совместимость, он бы не стал популярным.

Во-первых, слова «обратная совместимость» и лялекс не следует даже в одном предложении упоминать, потому что общего у них мало. Даже для юзерспейса, да. Удачи запустить проги 20-летней давности, особенно если они используют звук.

Во-вторых, лялекс стал популярным не столько из-за совместимости в юнуксами, сколько из-за дешевизны, поддержки нужного железа (x86), лицензионной чистоты и того факта, что он обладал всеми этими качествами в нужное время (бум доткомов). TL;DR гиганты юникса продавали сервера по $50k за штуку, аналогичный на редхате и интеле можно было взять в 10 раз дешевле. Вот поэтому лялекс взлетел.

А большое количество старого софта всё ещё делает fork() + exec*() по-старинке.

Как и нового. Привычку быдлокодить вообще трудно исправить. Но fork() всё равно является лютым недоразумением, и не только из-за overcommit.

yorshka
() автор топика
Ответ на: комментарий от yorshka

Ты про fork()?

этот костыль можно смело выкинуть и заменить на posix_spawn().

И как это поможет избавиться от overcommit ? Нет, в ответ на вопрос не надо объяснять, как работает форк. Надо объяснить, как станет ненужным overcommit.

r--r--r--
()
Последнее исправление: r--r--r-- (всего исправлений: 1)
Ответ на: комментарий от r--r--r--

И как это поможет избавиться от overcommit ?

Как удаление одной из главных причин существования overcommit позволит избавиться от overcommit? Ну, я даже не знаю.

yorshka
() автор топика
Ответ на: комментарий от Stanson

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

mittorn ★★★★★
()
Ответ на: комментарий от r--r--r--

Отлично! Продолжай так думать. Если ты не способен понять ироническое замечание, то о чём с тобой вообще общаться?

Кстати, в отличных от UNIX системах нет overcommit и всё классно работает. В Windows, например. Не хватает памяти при аллокации? Нехрен жрать, получай NULL. Возможно, разработчики NT (и VMS до неё) что-то подозревали, особенно учитывая, что UNIX тогда уже почти 20 лет существовал, а его косяки были хорошо изучены и разобраны по полочкам.

yorshka
() автор топика
Ответ на: комментарий от yorshka

Давно уже придумали.

И как, популярность набрала?

Удачи запустить проги 20-летней давности, особенно если они используют звук.

Ну давай пример прог. А как они звук используют?

Как бы

sudo apt install osspd && dd if=/vmlinuz of=/dev/dsp bs=1024 count=10
у меня заработало.

shdown ★★
()
Ответ на: комментарий от yorshka

К сожалению, в современном лялексе с включенным overcommit этой ошибки не возникает в том виде, о котором ты пишешь

это уже нюансы платформы. Ошибка всё равно возникает при попытке указать неадекватный размер, превышающий адресное пространство, так что обработка её всё ещё необходима. Да и оверкоммит могут перенастроить

А чем это лучше копирования-то?

копирование - O(n*m), где m - размер элемента в байтах, а в случае с некой рутиной, исправляющей ссылки (если это вообще нужно делать), там будет m количеством этих ссылок.
std::vector не делает какой-то магии, позволяющей не думать о перемещении элементов в памяти, потому не совсем понятно, почему он вообще должен исправлять ссылки внутри, пересоздавая элементы

mittorn ★★★★★
()
Ответ на: комментарий от shdown

Да особо незачем, разве чтобы запечь готовую структуру в бинарь, а не конструировать её в рантайме

mittorn ★★★★★
()
Ответ на: комментарий от shdown

И как, популярность набрала?

Ну, да? Прямо у тебя в процессоре есть, например (если у тебя не AMD).

https://community.intel.com/t5/Blogs/Tech-Innovation/open-intel/ChkTag-x86-Memory-Safety/post/1721490

Ну давай пример прог. А как они звук используют?

Любой порт игрушки от Iculus, например. Heretic 2 из моих любимых.

sudo apt install osspd

Ээээ.. я так могу заявить, что Linux даже с вендой обратно совместим, потому что Wine и Proton. В самом ядре поддержки OSS нет уже лет 20 как, а костыли не считаются.

yorshka
() автор топика
Ответ на: комментарий от mittorn

копирование - O(n*m), где m - размер элемента в байтах,

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

std::vector не делает какой-то магии, позволяющей не думать о перемещении элементов в памяти, потому не совсем понятно, почему он вообще должен исправлять ссылки внутри, пересоздавая элементы

Потому что std::vector не пересоздаёт элементы из ниоткуда. При ресайзе дёргается конструктор перемещения, если он есть и помечен как noexcept, либо конструктор копирования. Исправление ссылок можно делать в одном из них.

Короче, я это веду к тому, что для универсального контейнера std::vector вполне норм. Если требуется что-то дико специфическое, то пиши сам.

yorshka
() автор топика
Ответ на: комментарий от yorshka

Ну, да? Прямо у тебя в процессоре есть, например (если у тебя не AMD).

Э…

14 окт. 2025 г. — ChkTag пока существует только на бумаге, поэтому до появления программной поддержки пройдёт ещё немало времени — как минимум несколько лет. И ещё несколько лет — до появления аппаратной поддержки, если это расширение будет принято сообществом.

У меня в процессоре есть? Правда? Может, конкретные инструкции покажешь?

shdown ★★
()
Ответ на: комментарий от mittorn

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

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

Stanson ★★★★★
()
Ответ на: комментарий от eao197

Так зачем прогресс останавливать?

Как будто у вас базовые возможности C++98 кто-то отнимает.

Это психология «илитных» мазохистов – «не зае-лся – не старался» (казалось бы… но на асме почему-то не пишут, видать дрова рубить лежа неудобно). Одни упоротые считают что «знают лучше» и фигачат лисапеты на каждый чих, где «ПППРОИЗВОДИТЕЛЬНОСТЬ!» достигается исключительно «ковбойскими» способами (пока пострадавшие от мушку не провернули), потом толпа нубья считает что «не сделал свой лисапет из говна и палок – не плюсист». Им для щастья надо восход-закат вручную на основе заученных замшелых методичек по «дофига оптимизации», где нужда выдавалась за добродетель. А когда компилятор похеривает и игнорирует их «усилия» у них инсульт жопы происходит от того что плюсов от их превозмогания давно нет никаких, только косые крестики хх на ихних слоптимизациях и фрустрации от того что они ничего больше толком не контролируют, а «царский код»(ТМ) без -fpermissive даже не собирается.

anonymous
()
Ответ на: комментарий от shdown

Я не далее как месяц назад показывал запуск не абы чего, а аж фаерфокса из 2008-го на убунте 2204: Вышло издание 2,92 книги «Программирование: введение в профессию» А. В. Столярова (комментарий)

unC0Rr ★★★★★
()
Ответ на: комментарий от shdown

ChkTag пока существует только на бумаге

Пройди дальше по ссылкам, балда. LAM уже есть в железе и с поддержкой в Linux. Лично у тебя, возможно, нет, но процессоры с ним давно продаются.

А вообще, pointer tagging – штука древняя как мир. Лисп-машины на этом во многом работали.

yorshka
() автор топика
  • Markdown
Пустая строка (два раза Enter) начинает новый абзац. Знак '>' в начале абзаца выделяет абзац курсивом цитирования.
Внимание: прочитайте описание разметки Markdown.
Используйте Ctrl-Enter для размещения комментария