Грокаем C++
5.5K subscribers
18 photos
3 files
295 links
Два сеньора C++ - Владимир и Денис - отныне ваши гиды в этом дремучем мире плюсов.

По всем вопросам - @ninjatelegramm

Менеджер: @Spiral_Yuri
Реклама: https://telega.in/c/grokaemcpp
Мы на TGstat: https://tgstat.ru/channel/@grokaemcpp/stat
Download Telegram
Приветственный пост

Рады приветствовать всех на нашем канале!
Вы устали от скучного, монотонного, обезличенного контента по плюсам?

Тогда мы идем к вам!

Здесь не будет бесполезных 30 IQ постов, сгенеренных ChatGPT, накрученных подписчиков и активности.

Канал ведут два сеньора, Денис и Владимир, которые искренне хотят делится своими знаниями по С++ и создать самое уютное коммьюнити позитивных прогеров в телеге!
(ну вы поняли, да? с++, плюс плюс, плюс типа
позитивный?.. ай ладно)

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

ГАЙДЫ:

Мини-гайд по собеседования
Гайд по категория выражения и мув-семантике
Гайд по inline

Дальше пойдет список хэштегов, которыми вы можете пользоваться для более удобной навигации по каналу и для быстрого поиска группы постов по интересующей теме:
#algorithms
#datastructures
#cppcore
#stl
#goodoldc
#cpp11
#cpp14
#cpp17
#cpp20
#commercial
#net
#database
#hardcore
#memory
#goodpractice
#howitworks
#NONSTANDARD
#interview
#digest
#OS
#tools
#optimization
#performance
#fun
#compiler
#multitasking
#design
#exception
#guide
#задачки
#base
#quiz
#concurrency
Терминал

Вот иногда живешь-живешь, учишь иностранный язык или в какую-то другую сферу погружаешься, и в какой-то момент тебе приходит озарение по поводу ориджина простых вещей, которые мы все принимаем как данность. Например, слово банкнота. Для нас это одна единица бумажных денег. И мы не задумываемся, почему это слово обозначает одну деньгу. А все просто. Записка из банка. Bank note. Взорвало мозг? Если нет, то вы либо очень умный, либо потеряли энтузиазм к жизни.

Хочу поделиться с вами похожим приколом только из мира computer science. Думаю, что все мы хоть раз в жизни открывали графический терминал на своих Unix системах(реальных или виртуальных), ну или хотя бы подключались удалённо к ним. Все-таки, знание команд для unix - это маст хэв и де факто стандарт для сферы разработки. Если вы хоть раз разрабатывали не локально, то с 99% вероятности вы подключались к Линукс системе и ей надо бы уметь управлять.

Ну дак вот. Помните, какие раньше были компьютеры? Я вот тоже не помню, потому застал время уже полностью персональных компьютеров, где все было соединено вместе. А лет 50 назад нормальной практикой в компании было иметь один здоровый ЭВМ, размером с самомнение веганов, и много-много отдельных «терминалов», через которые сотрудники могли общаться с эвм. Они имели клавиатуру, дисплей, печатающее устройство, динамик и ещё пару простых прибамбасов. Пользователь вводит команду, команда по проводам попадает в эвм, обрабатывается и передаётся в виде текстовой или графической информации на терминал.

Мы сейчас делаем тоже самое, только виртуально. Открываем окошко, через которое управляем системой. Правда все мы воспринимаем это как данность и как обыкновенный, так и задуманный способ взаимодействия с компьютером. Терминал - это симулякр в чистом виде.

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

Stay surprised. Stay cool.

#fun #tools #OS
Понимание режима ядра Linux

Режим ядра Linux — это сердце операционной системы. Это один из двух режимов работы программных средств и он работает с наивысшими привилегиями и отвечает за управление оборудованием, драйверами устройств, памятью, файловыми системами и планированием процессов. Вот некоторые ключевые функции режима ядра Linux:

1️⃣ Абстракция оборудования. Ядро управляет взаимодействием с аппаратными устройствами такими, как дисководы, сетевые интерфейсы и контроллеры ввода/вывода. Оно обеспечивает унифицированный интерфейс, который позволяет избежать аппаратных сложностей.

2️⃣ Управление процессами. Кернел отвечает за распределение времени ЦП между выполняющимися задачами. Оно создает такие сущности как потоки и процессы, которые являются единицами исполнения кода и его окружением, а также диспетчер, который и реализует алгоритмы распределения времени.

3️⃣ Управление памятью. Ядро распределяет пространство ОЗУ между процессами с помощью механизма страничного отображения - выделяет и освобождает процессам страничные кадры физической памяти и отображает на страницы их адресного пространства. Это адресное пространство реализуется с помощью абстракции виртуальной памяти, где каждому процессу принадлежит весь спектр виртуальных адресов, которые мэтчатся с адресами реальной памяти.

4️⃣ Управление файловой системой. Она предоставляет процессам унифицированный интерфейс файлового доступа к ПЗУ. Она также организует взаимодействие с другими системами. Например, доступ с CD/DVD-накопителю через файл /dev/sr0, к мыши - через /dev/input/mouse0, доступ процессов к страницам памяти друг друга - через файлы /proc/PID/mem, и тд.

5️⃣ Управление устройствами ввода-вывода. Эта подсистема распределяет доступ к устройствам ввода-вывода между процессами и предоставляет унифицированный интерфейс для чтения/записи. Для устройств ВЗУ она организует кэширование с помощью подсистемы управления памятью.

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

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

Stay based. Stay cool.

#OS
Почему не нужно указывать размер освобождаемого блока для free()

Второй пост в формате телеграф статьи. Поговорим о том, как так вышло, что не нужно указывать размер освобождаемой памяти для функции free. Поговорим про API, отправимся в прошлое на 40 лет назад и представим, как принималось это решение.

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

Ссылочка на статью: https://telegra.ph/Pochemu-ne-nuzhno-ukazyvat-razmer-osvobozhdaemogo-bloka-dlya-free-12-07

Stay cool.

#hardcore #OS #memory #howitworks
Доступ к режиму ядра Linux

По умолчанию программы и приложения пользовательского пространства работают в режиме с более низкими привилегиями, называемом пользовательским режимом. Почему? Да потому что мы своими сардельками такого можем понаписать, что все с первого же запуска на*бнется. Чтобы защитить систему от случайного и специального негативного вмешательства и придуман user mode. Однако существуют способы получить доступ к режиму ядра Linux для конкретных задач. Вот самые основные из них:

1️⃣ Системные вызовы. Программы пользовательского пространства могут выполнять системные вызовы для запроса служб ядра. Системные вызовы предоставляют интерфейс, который позволяет получать доступ к функциям ядра, таким как файловые операции или сетевое взаимодействие. Все, что нужно серьезным программам - создание потока, процесса, чтение и запись в сокет, создание шаренной памяти - все это делается с помощью системных вызовов. Да, вы могли сами их никогда не использовать. Но вы постоянно используете библиотеки, которые это делают за вас.

2️⃣ Аппаратные прерывания. Они генерируются периферийными устройствами при наступлении определенных событий (например, завершение дисковой операции ввода/вывода или поступление данных на последовательный порт) и имеют асинхронный характер, поскольку невозможно точно сказать, в какой момент наступит то или иное прерывание. Более того, эти прерывания, как правило, не связаны с текущим процессом, а вызваны внешними событиями.

3️⃣ Особые ситуации. Они вызваны самим процессом, и связаны с выполнением тех или иных инструкций, например, деление на ноль или обращение к несуществующей странице памяти. Таким образом, обработка особых ситуаций производится в контексте процесса, при этом может использоваться его адресное пространство, а сам процесс — при необходимости блокироваться (перемещаться в состояние сна).

4️⃣ Ну и если уж вы взрослый и толстый дядя, то наверняка способны написать свой модуль ядра. Linux предоставляет мощный и обширный API для приложений, но иногда его недостаточно. Для взаимодействия с оборудованием или осуществления операций с доступом к привилегированной информации в системе может понадобиться новый модуль ядра. Например, драйвер для вашего самодельного устройства, чтобы с ним можно было общаться.

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

Stay careful. Stay cool.

#OS
Как система может выделить 131 Терабайт оперативы?

Здесь мы выясняли, сколько же памяти может нам выдать система. И ответ для многих оказался неожиданным. 131 тарабайт - в дохренальен раз больше, чем реальный объем RAM на тестовой машине. Понятное дело, что это фейковые терабайты, потому что их просто негде расположить. И если бы было хотя бы RAMx2, можно было бы еще поговорить про такие штуки, как файлы подкачки. Но здесь прям совсем ничего не сходится, поэтому погнали разбираться, что к чему. Повторю ремарку, что здесь я говорю про 64-битные системы.

Первая подсказка к ответу для вас - практически в точности такой же результат я получил на других своих машинах. Да и под тем постом @dtbeaver оставил скрин, что у него такие же цифры +- 2 Гб от того, что получил я. Значит этот предел - общий для, по крайней мере, большой группы линуксоидов с 64-битными системами. Это наводит на вопрос: а сколько вообще можно адресовать памяти? Может 131 Тб и есть это количество?

Вторая подсказка - выделилось на самом деле не 131(ох уж это эти десятичные приставки в двоичном мире...), а 128. До боли знакомое число...

Однажды на собесе меня спросили: сколько байт я могу адресовать в программе? И я ответил: 2^64 байт. Ну вот у нас есть указатель. Он занимает 8 байт или 64-бит памяти. Минимально адресуемый размер памяти - 1 байт. И получается, что 8 байт памяти могут хранить 2^64 уникальных чисел и, соответственно, именно столько байт и могут быть адресованы. У меня этот ответ приняли, типа я ответил правильно. Но я ошибался....

Для начала вспомним, как вообще данные программы маппятся на физическую память. Напрямую использовать физические адреса мы не можем, потому что тогда каждый процесс должен был знать о том, какие ячейки уже используются, чтобы не нарваться на конфликт. Поэтому придумали такую абстракцию - виртуальная память. Теперь каждый процесс думает, что он пуп вселенной и ему одному принадлежит вся память компьютера. Теперь процессу ничего не нужно знать, он просто кайфует и оперирует всем адресным пространством единолично. А грязной работой занимается ОС. А раз процессу "принадлежит" вся память компьютера, то в теории ему и доступны все те 2^64 байта для размещения своих данных.

Но на самом деле в современных системах для адресации используются только 48 бит адреса. Почему не все 64? 48-бит - это 256 Тб оперативной памяти. Нет таких промышленных систем, которые бы обладали таким объемом оперативной+swap памяти. Сейчас уже конечно стали появляться, поэтому появляются системы с 52/57 адресными битами, но сегодня не об этом. Представим, что их нет. Тогда введение возможности адресовать все 2^64 байта виртуальной памяти будет увеличивать сложность и нагрузку на преобразование виртуального адреса в физический. Зачем платить за то, чем не пользуешься? Да и 64-битная адресация потребовала бы больший размер страниц, больший размер таблиц страниц или большую глубину страничной структуры. Это все увеличивает стоимость кеш промаха в буфере ассоциативной трансляции (TLB). В общем, накладные расходы были бы больше. А никому этого не надо, пока у нас нет столько памяти.

Но вы спросите у меня: 128 терабайт - это 2^47, а ты нам говоришь, что 48 бит адресуются. Куда делся еще один бит, ааа?

Операционная система, как главный дерижер всех процессов в системе, может вмешиваться в их работу по самым разным причинам. Ну например, через системные вызовы. Поэтому в ОС нужно иметь возможность в адресном пространстве конкретного процесса адресовать свой код и свои данные. Поэтому операционка делает свою виртуальную память видимой в адресном пространстве каждого процесса. Это значит, что 2^48 байт делятся между адресным пространством пользователя (user space) и ядра (kernel space). История встречала разные отношения в этом разделении. Но сейчас более-менее все остановились на соотношении 1:1. То есть 256 терабайт делятся поровну между пользовательским процессом и системой. Положительную часть берет себе система, а отрицательную - процесс. Так и получаются те самые 128 Тб.

Продолжение в комментариях

#memory #OS #fun #hardcore
Уникален ли std::thread::id среди всех процессов?

std::thread::id используется как уникальный идентификатор потока внутри вашего приложения. Но тут возникает интересный вопрос. А вот допустим я запустил 2 инстанса моего многопоточного приложения. Могу ли я гаранировать, что айди потоков будут уникальны между двумя инстансами? Например, я хочу какую-то общую для всех инстансов логику сделать, основанную на идентификаторах потоков. Могу ли я положиться на их уникальность? Или даже более общий вопрос: уникален ли std::thread::id среди всех процессов в системе?

Начнем с того, что стандарт С++ ничего не знает про процессы. Точно так же, как и до С++11, стандарт ничего не знал про потоки. У нас нет никаких стандартных инструментов(syscall - это не плюсовый инструмент) для работы с процессами, их запуском или для общения между процессами. И раз это не специфицировано стандратном, мы не можем ничего гарантировать. Потому что никаких гарантий и нет. Единственная гарантия стандарта относительно std::thread::id - идентификаторы - уникальны для каждого потока выполнения и могут переиспользоваться из уничтоженных потоков. Но давайте посмотрим немного глубже, на основу std::thread. Возможно там мы найдем ответ.

И тут есть 2 основных варианта. Для unix-подобных систем std::thread реализован на основе pthreads. Для виндовса это будет Windows Thread API.

В доках pthreads написано: "Thread IDs are guaranteed to be unique only within a process". Так что на юникс системах идентификатор потока уникален только в пределах одного процесса и может повторяться в разных процессах.

А вот доках Win32 API написано следующее: "Until the thread terminates, the thread identifier uniquely identifies the thread throughout the system". Оказывается, что на винде айди потока уникален среди всех процессов. Эти айдишники выдаются из одного пула, поэтому их значения синхронизированы сквозь все процессы.

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

Stay unique all over the world. Stay cool.

#cpp11 #cppcore #OS #multitasking