Стек си: Реализация стека на си
Структура приложения на си
Теги: Си структура приложения, ro-data, rw-data, bss сегмент, куча, стек, сегмент кода
Сегментация приложения на си
В этой статье общё описывается структура приложения, которую обычно можно встретить на практике. Стандарт языка си не определяет структуры приложения,
поэтому неверно говорить, например, о том, что в приложении на си автоматические перемные располагаются на стеке, а статические в bss сегменте и т.п. Однако,
реальные примеры помогают глубже понять поведение компилятора и программы.
Перед тем, как начать разговор о динамической работе с памятью, необходимо рассмотреть структуру приложения.
В памяти компьютера программа организована следующим образом
- Сегмент данных (Data, bss-сегмент и куча)
- Стек
- Сегмент кода
Структура программы на си.
Data cостоит из статических и глобальных переменных, которые явно инициализируются значениями. Этот сегмент может быть далее разбит на ro-data (read only data) – сегмент данных только для чтения, и rw-data (read write data) – сегмент данных для чтения и записи.
Например, глобальные переменные
s[] = "hello world" int debug=1
Будут храниться в rw-области. Для выражения типа
const char* string = "hello world"
указатель будет храниться в rw-области, а строкой литерал «hello world» в ro-области.
static int i = 10 int i = 10
оба будут расположены в сегменте данных.
BSS-сегмент (block started by symbol) содержит неинициализированные глобальные переменные, или статические переменные без явной инициализации.
Этот сегмент начинается непосредственно за data-сегментом. Обычно загрузчик программ инициализирует bss область при загрузке приложения нулями.
Дело в том, что в data области переменные инициализированы – то есть затирают своими значениями выделенную область памяти. Так как переменные в bss
области не инициализированы явно, то они теоретически могли бы иметь значение, которое ранее хранилось в этой области, а это уязвимость, которая
предоставляет доступ до, возможно, приватных данных. Поэтому загрузчик вынужден обнулять все значения. За счёт этого и неинициализированные глобальные
переменные, и статические переменные по умолчанию равны нулю.
Куча – начинается за BSS сегментом и начиная оттуда растёт, соответственно с увеличением адреса. Этот участок используется для выделения на нём памяти с использованием
функции malloc (и прочих) и для очисти с помощью функции free.
Стек вызовов обычно растёт «навстречу» куче, то есть с увеличением стека адрес вершины стека уменьшается. Набор значений, которые кладутся на стек одной функцией, называются
фреймом. После выхода из функции стек освобождается от фрейма. Один фрейм хранит как минимум одно значение — адрес возврата.
Каждый раз, когда мы создаём локальные переменные, они располагаются на стеке. Как только мы выходим из функции, стек очищается и переменные исчезают. Вызов функций и передача параметров также происходит через стек.
Сегмент кода, или текстовый сегмент, или просто текст, содержит исполняемые инструкции. У него фиксированный размер и обычно он используется только для чтения, если же в него можно
писать, то архитектура поддерживает самомодификацию.
Сегмент кода располагается после начала стека, поэтому в случае роста он [стек] не перекрывает сегмент кода.
ru-Cyrl
18-
tutorial
Sypachev S.S.
1989-04-14
[email protected]
Stepan
Sypachev
students
Q&A
Всё ещё не понятно? – пиши вопросы на ящик
Путешествие по Стеку. Часть 1 / Блог компании Smart-Soft / Хабр
В предыдущих материалах мы рассмотрели размещение программы в памяти – одну из центральных концепций, касающихся выполнения программ на компьютерах. Теперь обратимся к стеку вызовов – рабочей лошадке большинства языков программирования и виртуальных машин. Нас ожидает знакомство с удивительными вещами вроде функций-замыканий, переполнений буфера и рекурсии. Однако всему свое время – в начале нужно составить базовое представление о том, как работает стек.
Стек имеет такое важное значение, потому что благодаря ему любая функция «знает» куда возвращать управление после завершения; функция же, в свою очередь — это базовый строительный блок программы. Вообще, программы внутренне устроены довольно просто. Программа состоит из функций, функции могут вызывать другие функции, в процессе своей работы любая функция помещает данные в стек и снимает их оттуда. Если нужно, чтобы данные продолжили существовать после завершения функции, то место под них выделяется не в стеке, а в куче. Вышесказанное в равной степени относится как к программам, написанным на относительно низкоуровневом C, так и к интерпретируемым языкам вроде JavaScript и C#. Знание данных вещей обязательно пригодится — и если придется отлаживать программу, и если доведется заниматься тонкой подстройкой производительности, да и просто для того, чтобы понимать, что же там, все-таки творится внутри программы.
Итак, начнем. Как только мы вызываем функцию, в стеке для нее создается стековый кадр. Стековый кадр содержит локальные переменные, а также аргументы, которые были переданы вызывающей функцией. Помимо этого кадр содержит служебную информацию, которая используется вызванной функцией, чтобы в нужный момент возвратить управление вызвавшей функции. Точное содержание стека и схема его размещения в памяти могут быть разными в зависимости от процессорной архитектуры и используемой конвенции вызова. В данной статье мы рассматриваем стек на архитектуре x86 с конвенцией вызова, принятой в языке C (cdecl). На рисунке вверху изображен стековый кадр, разместившийся у верхушки стека.
Сразу бросаются в глаза три процессорных регистра. Указатель стека, esp, предназначается для того, чтобы указывать на верхушку стека. Вплотную к верхуше всегда находится объект, который был добавлен в стек, но еще оттуда не снят. Точно также в реальной жизни обстоят дела со стопкой тарелок или пачкой 100-долларовых банкнот.
Хранимый в регистре esp адрес изменяется по мере того, как объекты добавляются и снимаются со стека, однако он всегда указывает на последний добавленный и еще не снятый со стека объект. Многие процессорные инструкции изменяют значение регистра esp как побочный результат своего выполнения. Реализовать работу со стеком без регистра esp было бы проблематично.
В случае с процессорами Intel, ровно как и со многими другими архитетурами, стек растет в направлении меньших адресов памяти. Поэтому верхушка, в данном случае, соответствует наименьшему адресу в стеке, по которому хранятся валидные используемые данные: в нашем случае это переменная local_buffer. Думаю, должно быть понятно, что означает стрелка от esp к local_buffer. Здесь все, как говорится, по делу – стрелка указывает точно на первый байт, занимаемый local_buffer, и это соответствует тому адресу, который хранится в регистре esp.
Далее на очереди еще один регистр, используемый для отслеживания позиций в стеке – регистр ebp – базовый указатель или указатель базы стекового кадра. Данный регистр предназначен для того, чтобы указывать на позицию в стековом кадре. Благодаря регистру ebp текущая функция всегда имеет своего рода точку отсчёта для доступа к аргументам и локальным переменным. Хранимый в регистре адрес изменяется, когда функция начинает или прекращает выполнение. Мы можем довольно просто адресовать любой объект в стековом кадре как смещение относительно ebp, что и показано на рисунке.
В отличии от esp, манипуляции с регистром ebp осуществляется в основном самой программой, а не процессором. Иногда можно добиться выигрыша в производительности просто отказавшись от страндартного использования регистра ebp – за это отвечают некоторые флаги компилятора. Ядро Linux – пример того, где используется такой прием.
Наконец, регистр eax традиционно используется для хранения данных, возвращаемых вызвавшей функции — это высказывание справедливо для большинства поддерживаемых в языке C типов.
Теперь давайте разберем данные, содержащиеся в стековом кадре. Рисунок показывает точное побайтовое содержимое кадра, c направлением роста адресов слево-направо – это то, что мы обычно видим в отладчике. А вот и сам рисунок:
Локальная переменная local_buffer – это массив байт, представляющий собой нуль-терминированную ASCII-строку; такие строки — неизменный атрибут всех программ на C. Размер строки — 7 байт, и, скорее всего, она была получена в результате клавиатурного ввода или чтения из файла. В нашем массиве может храниться 8 байт и, следовательно, один байт остается неиспользуемым. Значение этого байта неизвестно. Дело в том, что, данные то и дело добавлются и снимаются со стека, и в этом «бесконечном танце операции добавления и снятия» никогда нельзя знать заранее, что содержит память, пока не осуществишь в нее запись. Компилятор языка C не обременяет себя тем, чтобы иницилизировать стековый кадр нулями. Поэтому содержащиеся там данные заранее неизвестны и являются в некоторой степени случайными. Уж сколько крови попило такое поведение компилятора у программистов!
Идем далее. local1 – 4-байтовое целое число, и на рисунке видно содержимое каждого байта. Кажется, что это большое число – только взгляните на все эти нули после восьмерки, однако здесь наша интуиция сослужила нам дурную службу.
Процессоры Intel используют прямой порядок байтов (дословно «остроконечный»), и это значит, что числа хранятся в памяти начиная с младшего байта. Иными словами, самый младший значащий байт хранится в ячейке памяти с наименьшим адресом. На рисунках и схемах байты многобайтовых чисел традиционно изображаются в порядке слева-направо. В случае с прямым порядком байт, самый младший значащий байт будет изображен в крайней левой позиции, что отличается от привычного нам способа представления и записи чисел.
Неплохо знать о том, что вся эта «остроконечная / тупоконечная» терминология восходит к произведению Джонатана Свифта «Путешествия Гулливера». Подобно тому, как жители Лилипутии чистили яйцо с острого конца, процессоры Intel тоже обрабатывают числа начиная с младшего байта.
Таким образом, переменная local1 в действительности хранит число 8 (да-да, прям как количество щупалец у осьминога). Что касается param1, то там во втором от начала октете изображена двойка, поэтому в результате получаем число 2 * 256 = 512 (мы умножаем на 256, потому что каждый октет – это диапазон от 0 до 255). param2 хранит число 1 * 256 * 256 = 65536.
Служебная информация стекового кадра включает в себя два компонента: адрес стекового кадра вызвавшей функции (на рисунке — saved ebp) и адрес инструкции, куда необходимо передать управление по завершении данной функции (на рисунке – return address). Эта информация делает возможным возвращение управления, и следовательно, дальнейшее выполнение программы как будто никакого вызова и не было.
Теперь давайте рассмотрим процесс «рождения» стекового кадра. Стек растет не в том направлении, которое обычно ожидают увидеть, и сначала это может сбивать с толку. Например, чтобы увеличить стек на 8 байт, программист вычитает 8 из значения, хранимого в регистре esp. Вычитание – странный способ что-либо увеличить. Забавно, не правда ли!
Возьмем для примера простенькую программу на C:
Предположим, программу запустили без параметров в командной строке. При выполнении «сишной» программы в Linux, первым делом управление получает код, содержащийся в стандартной библиотеке C. Этот код вызовет функцию main() нашей программы, и, в данном случае, переменная argc будет равна 0 (на самом деле, переменная будет равна «1», что соответствует параметру — названию, под которым запущена программа, но давайте для простоты это момент сейчас опустим). При вызове функции main() происходит следующее:
Шаг 2 и 3, а также 4 (описан ниже) соответствуют последовательности инструкций, которая называется «прологом» и встречается практически в любой функции: текущее значение регистра ebp помещается в стек, затем значение регистра esp копируется в регистр ebp, что фактически приводит к созданию нового стекового кадра. Пролог функции main() такой же, как и других функций, с той лишь разницей, что при начале выполнения программы регистр ebp содержит нули.
Если взглянуть на то, что располагается в стеке под argc, то будут видны еще некоторые данные – указатель на строку-название, под которым программа была запущена, указатели на строки-параметры, переданные через командную строку (традиционный C-массив argv), а также указатели на переменные среды и непосредствено сами эти переменные. Однако, на данном этапе нам это не особо важно, так что продолжаем двигаться по направлению к вызову функции add():
Функция main() сначала вычетает 12 из текущего значения в регистре esp для выделения нужного ей места и затем присваивает значения переменным a и b. Значения, хранимые в памяти, изображены на рисунке в шестнадцатеричной форме и с прямым порядком байтов – как и в любом отладчике. После присвоения значений, функция main() вызывает функцию add(), и та начинает выполняться:
Чем дальше, тем интересней! Перед нами еще один пролог, однако теперь уже четко видно как последовательность стековых кадров в стеке оказывается организованной в связный список, и регистр ebp хранит ссылку на первый элемент этого списка. Вот с опорой на это и реализованы трассировка стека в отладчиках и Exception-объекты высокоуровневых языков. Обратим внимание на типичную для начала выполнения функции ситуацию, когда регистры ebp и esp указывают в одно и то же место. И еще раз вспомним, что для наращивания стека осуществляется вычитание из значения, хранящегося в регистре esp.
Важно заметить следующее — при копировании данных из регистра ebp в память происходит непонятное на первый взгляд изменение порядка хранения байтов. Дело в том, что для регистров такого понятия как «порядок байтов» не существует. Иными словами, рассматривая регистр, мы не можем говорить о том, что в нем есть «старшие или младшие адреса». Поэтому отладчики показывают значения, хранимые в регистрах, в наиболее удобном для человеческого восприятия виде: от более значимых к менее значимым цифрам. Таким образом, имея стандартную нотацию «слева-направо» и «little-endian» машину, создается обманчивое впечатление, что в результате операции копирования из регистра в память байты поменяли порядок на обратный. Я хотел, чтобы картина, показанная на рисунках была максимально приближена к реальности – отсюда и такие рисунки.
Теперь, когда самая сложная часть у нас позади, осуществляем сложение:
Здесь у нас появляется неизвестный регистр, чтобы помочь со сложением, но в целом ничего особенного или удивительного. Функция add() выполняет вою работу и, начиная с этого момента все действия в стеке будут осуществляться в обратном порядке. Но об этом расскажем как-нибудь в другой раз.
Все, кто дочитал до этих строк, заслуживает подарок за стойкость, поэтому я с огромной гиковской гордостью презентую Вам вот эту единую схемку, на которой изображены все вышеописанные шаги.
Не так уж все и сложно, стоит только разложить все по полочкам. Кстати, маленькие квадратики очень сильно помогают понимаю. Без «маленьких квадратиков» в информатике вообще никуда. Надеюсь, мои рисунки позволили составить ясную картину происходящего, на которой интуитивно просто показан и рост стека, и изменения содержимого памяти. При ближайшем рассмотрении, наше программное обеспечение не так уж и сильно отличается от простой машины Тьюринга.
На этом завершается первая часть нашего путешествия по стеку. В будущих статьях нас ждут новые погружения в «байтовые» дебри, после чего посмотрим, что же на этом фундаменте способны выстроить высокоуровневые языки программирования. Увидимся на следующей неделе.
Материал подготовлен сотрудниками компании Smart-Soft
smart-soft.ru
Как и зачем делать очередь на двух стеках / Хабр
Привет, Хабр!
Данный пост написан для новичков в олимпиадном программировании и начинающих разработчиков, готовящихся к прохождению алгоритмических интервью. В конце бонусная задачка. Если заинтересовал, прошу под кат 🙂
Для начала вспомним основы:
Стек
Стек реализует принцип LIFO (англ. last in — first out, «последним пришёл — первым вышел»).
Стек имеет две основные операции:
- push — положить элемент на стек
- pop — достать элемент из стека
Стек обычно реализуется на массиве (еще можно на связном списке). Подробнее про стек и его реализацию можно прочитать здесь
Очередь
Очередь — это структура данных с доступом к элементам FIFO (англ. First In, First Out – «первым пришёл — первым ушёл»)
Очередь имеет два основных метода в своем интерфейсе:
- push — положить элемент в конец очереди
- pop — достать элемент из начала очереди
Подробнее про обычную очередь можно прочитать здесь.
Обычно рассматривают два базовых подхода к реализации очереди:
- На массиве
- На связном списке
Почему стек круче, чем очередь
Представим себе, что наша структура данных должна поддерживать следующий набор операций:
- Поддержка минимума (min)
- Поддержка максимума (max)
- Поддержка gcd (англ. greatest common divisor — наибольший общий делитель)
- Поддержка lcm (англ. least common multiple — наименьшее общее кратное)
Все перечисленные операции ассоциативны (образуют полугруппу), но ни одна из них не имеет обратного элемента (не образует группу).
Для стека можно очень просто поддерживать любую из приведенных операций: достаточно хранить на его вершине пару:
, где второй элемент пары — результат операции, примененной ко всем элементам стека.
Ниже пример реализации стека с поддержкой минимума на Python:
class Stack:
def __init__(self):
self.stack = []
def __bool__(self):
return bool(self.stack)
def push(self, elem):
if self.stack:
self.stack.append((elem, min(elem, self.stack[-1][1])))
else:
self.stack.append((elem, elem))
def pop(self):
return self.stack.pop()[0]
def get_min(self):
if not self.stack:
return math.inf
return self.stack[-1][1]
А теперь подумайте, как проделать тот же фокус с очередью? Если пробовать с классической очередью, организованной на массиве, вряд ли что-то получится. Это связано с тем, что операция минимум не имеет обратную операцию (как операция сложения имеет операцию вычитания, например). Как вы могли догадаться, далее я расскажу о не совсем классической реализации очереди на двух стеках.
Очередь на двух стеках
Главное условие, которое должно быть выполнено — все операции должны выполняться за амортизированное O(1).
Возьмем два стека: s1 и s2.
Операцию push будем всегда делать в стек s1.
Операция pop будет устроена так: если стек s2 пустой, перекладываем все элементы из s1 в s2 последовательными вызовами pop и push. Теперь в стеке s2 лежат элементы в обратном порядке (самый верхний элемент — это самый первый положенный элемент в нашу очередь).
Если s2 не пуст, тупо достаем элементы из него. Как только s2 окажется снова пустым повторяем ту же операцию.
Код на Python:
class Queue:
def __init__(self):
self.s1 = Stack()
self.s2 = Stack()
def push(self, elem):
self.s1.push(elem)
def pop(self):
if not self.s2:
while self.s1:
self.s2.push(self.s1.pop())
return self.s2.pop()
def get_min(self):
return min(self.s1.get_min(), self.s2.get_min())
Время работы
Операция push: Мы тупо кладем элемент в стек s1. Время работы O(1).
Операция pop: Для каждого элемента мы делаем не более одного перекладывания из стека в стек, следовательно амортизированное время работы составит O(1).
Операция get_min: Для стеков s1 и s2 известны минимумы, поэтому мы просто берем минимум из минимумов. Время работы O(1).
Бонусная задачка
Дана последовательность N чисел. Задано число M Идея решения 1
Сделай очередь с поддержкой минимума и максимума.
Идея решения 2aamonster подсказал, что есть другой способ решения (читай здесь).
Заключение
Спасибо, что дочитали до конца!
Если вас заинтересовала тема, можете почитать как сделать персистентную очередь на двух стеках здесь, либо дождаться выхода следующего моего топика.
Пишите в комментариях какие задачи на двух стеках вам приходилось решать на интервью или контестах.
Что такое стек
Постепенно осваиваем способы организации и хранения данных. Уже было про деревья, попробуем про стеки. Это для тех, кто хочет в будущем серьёзно работать в ИТ: одна из фундаментальных концепций, которая влияет на качество вашего кода, но не касается какого-то конкретного языка программирования.
👉 Стек — это одна из структур данных. Структура данных — это то, как хранятся данные: например, связанные списки, деревья, очереди, множества, хеш-таблицы, карты и даже кучи (heap).
Как устроен стек
Стек хранит последовательность данных. Связаны данные так: каждый элемент указывает на тот, который нужно использовать следующим. Это линейная связь — данные идут друг за другом и нужно брать их по очереди. Из середины стека брать нельзя.
👉 Главный принцип работы стека — данные, которые попали в стек недавно, используются первыми. Чем раньше попал — тем позже используется. После использования элемент стека исчезает, и верхним становится следующий элемент.
Классический способ объяснения принципов стека звучит так: представьте, что вы моете посуду и складываете одинаковые чистые тарелки стопкой друг на друга. Каждая новая тарелка — это элемент стека, а вы просто добавляете их по одной в стек.
Когда кому-то понадобится тарелка, он не будет брать её снизу или из середины — он возьмёт первую сверху, потом следующую и так далее.
🤔 Есть структура данных, похожая на стек, — называется очередь, или queue. Если в стеке кто последний пришёл, того первым заберут, то в очереди наоборот: кто раньше пришёл, тот раньше ушёл. Можно представить очередь в магазине: кто раньше её занял, тот первый дошёл до кассы. Очередь — это тоже линейный набор данных, но обрабатывается по-другому.
Стек вызовов
В программировании есть два вида стека — стек вызовов и стек данных.
Когда в программе есть подпрограммы — процедуры и функции, — то компьютеру нужно помнить, где он прервался в основном коде, чтобы выполнить подпрограмму. После выполнения он должен вернуться обратно и продолжить выполнять основной код. При этом если подпрограмма возвращает какие-то данные, то их тоже нужно запомнить и передать в основной код.
Чтобы это реализовать, компьютер использует стек вызовов — специальную область памяти, где хранит данные о точках перехода между фрагментами кода.
Допустим, у нас есть программа, внутри которой есть три функции, причём одна из них внутри вызывает другую. Нарисуем, чтобы было понятнее:
Программа запускается, потом идёт вызов синей функции. Она выполняется, и программа продолжает с того места, где остановилась. Потом выполняется зелёная функция, которая вызывает красную. Пока красная не закончит работу, все остальные ждут. Как только красная закончилась — продолжается зелёная, а после её окончания программа продолжает свою работу с того же места.
А вот как стек помогает это реализовать на практике:
Программа дошла до синей функции, сохранила точку, куда ей вернуться после того, как закончится функция, и если функция вернёт какие-то данные, то программа тоже их получит. Когда синяя функция закончится и программа получит верхний элемент стека, он автоматически исчезнет. Стек снова пустой.
С зелёной функцией всё то же самое — в стек заносится точка возврата, и программа начинает выполнять зелёную функцию. Но внутри неё мы вызываем красную, и вот что происходит:
При вызове красной функции в стек помещается новый элемент с информацией о данных, точке возврата и указанием на следующий элемент. Это значит, что когда красная функция закончит работу, то компьютер возьмёт из стека адрес возврата и вернёт управление снова зелёной функции, а красный элемент исчезнет. Когда и зелёная закончит работу, то компьютер из стека возьмёт новый адрес возврата и продолжит работу со старого места.
Переполнение стека
Почти всегда стек вызовов хранится в оперативной памяти и имеет определённый размер. Если у вас будет много вложенных вызовов или рекурсия с очень большой глубиной вложенности, то может случиться такая ситуация:
- рекурсия всё работает и работает;
- при каждом новом витке рекурсии в стек добавляются новый элемент;
- когда элементов будет слишком много, память закончится, новые элементы некуда будет складывать и произойдёт переполнение стека.
Переполнение — это плохо: данные могут залезать в чужую область памяти и записывать себя вместо прежних данных. Это может привести к сбою в работе других программ или самого компьютера. Ещё таким образом можно внедрить в оперативную память вредоносный код: если программа плохо работает со стеком, можно специально вызвать переполнение и записать в память что-нибудь вредоносное.
Стек данных
Стек данных очень похож на стек вызовов: по сути, это одна большая переменная, похожая на список или массив. Его чаще всего используют для работы с другими сложными типами данных: например, быстрого обхода деревьев, поиска всех возможных маршрутов по графу, — и для анализа разветвлённых однотипных данных.
Стек данных работает по такому же принципу, как и стек вызовов — элемент, который добавили последним, должен использоваться первым.
Что дальше
А дальше поговорим про тип данных под названием «куча». Да, такой есть, и с ним тоже можно эффективно работать. Стей тюнед.
Текст и иллюстрации:
Миша Полянин
Редактор:
Максим Ильяхов
Корректор:
Ира Михеева
Иллюстратор:
Даня Берковский
Вёрстка:
Маша Дронова
Доставка:
Олег Вешкурцев
Мой новый стек веб-технологий для 2020 года / Блог компании RUVDS.com / Хабр
Помните те времена, когда стеки веб-технологий были простыми? Когда уровни этих стеков можно было обозначить в виде четырёхбуквенного сокращения вроде LAMP, LEMP или LEPP? Когда всё, что было нужно для создания и поддержки сайтов, сводилось к вполне обычному железу, к какому-нибудь опенсорсному софту, да к упорству в достижении цели?
Мой первый успешный сайт, теперь уже старинный проект 1999 года, был создан с использованием технологий, которые можно пересчитать по пальцам одной руки: HTML4, CSS2, JavaScript3 и Apache 1.1. Всё это крутилось на сервере с Linux 2.0. Сайт включал в себя 38000 страниц. И сегодня, через 20 лет, он всё ещё их выдаёт.
С тех пор всё изменилось. Это касается и стеков веб-технологий. Теперь они совсем не те, что прежде.
Автор статьи, перевод которой мы сегодня публикуем, хочет рассказать о том, как он перешёл от «фуллстека» к «стеку 2020 года». Некоторые технологии в ходе этого путешествия неожиданно стали фаворитами, а некоторые потеряли былую привлекательность.
Стек веб-технологий 2020 года
2020 год — это начало нового десятилетия. Это — время, когда стоит поговорить о новом стеке веб-технологий.
На что похож «стек 2020 года»? Надо сказать, что на это очень сильно влияет то, чего пытается достичь разработчик сайтов. Выбор подходящих уровней сильно зависит от того, какая степень масштабируемости требуется для проекта.
Меня особенно интересуют маленькие веб-сайты. Те, которые хорошо чувствуют себя на виртуальном сервере. Таким сайтам не нужны балансировщики нагрузки или постоянные хранилища данных. Это — ниша CMS, которую уже давно занимает WordPress. Но в основе всего этого лежит не некий минималистичный сервер. Вместо этого речь идёт о системе, которая может выдержать постоянный поток трафика без необходимости автоматического повышения её мощности в часы пик.
Сейчас для разработки и поддержки проектов в интересующей меня области я использую стек технологий, состоящий из 12 уровней.
▍1. Облачный провайдер
Базой моего стека является облачный провайдер, который учитывает потребности тех, кто привык сам заниматься тонкими настройками сред, в которых выполняются их веб-проекты. Я пользовался собственными серверами до тех пор, пока стоимость их поддержки не стала слишком высокой. Аренда места в серверной стойке, выделенный IP-адрес, обеспечение нужной полосы пропускания… Всё это вносит вклад в месячную стоимость физического сервера. Но настоящий «вымогатель» — это стоимость электричества. Облачные провайдеры гораздо дешевле, чем $1.25 в день, которые я отправлял поставщику электроэнергии. Отказ от подобных трат позволил мне сэкономить сотни долларов в год.
▍2. Дистрибутив Fedora Linux с SELinux
Безопасность — это то, что очень сильно всех нас беспокоит. SELinux можно сравнить с мощной охранной системой, работающей в Linux. Если к этому добавить ещё и хорошо настроенный iptables-файрвол, получится то, что позволит владельцу сайта спокойно спать по ночам. Если вы не уверены в том, что вам всё это нужно — проведите следующий эксперимент. Разверните новый сервер у вашего любимого облачного провайдера и понаблюдайте за тем, как скоро его начнут атаковать. Я видел, как брутфорс-атаки на новые сервера с попытками входа по SSH начинались менее чем через 10 минут после их создания.
▍3. Веб-сервер Read Write Serve
Я пользуюсь веб-сервером Read Write Serve с TLS-сертификатами от LetsEncrypt. Раньше я был фанатом Apache, на настройку и запуск новых веб-сайтов у меня уходило буквально несколько минут. Но с тех пор, как я перешёл с PHP на JavaScript, об Apache пришлось забыть. Сервер Express казался мне чрезвычайно простым инструментом, но лишь до тех пор, пока я не попытался воспроизвести в нём весь тот функционал, который давал мне Apache. Речь идёт о механизме согласования содержимого, об условном кэшировании, о сжатии данных, о перезаписи URL для SEO, о CORS, о политиках защиты контента. В результате я и перешёл на сервер Read Write Serve, в котором все эти возможности присутствуют по умолчанию.
▍4. Среда выполнения приложений Node.js
За логику приложения, выполняющуюся на сервере, отвечает среда Node.js. Возникает такое ощущение, что в экосистеме NPM имеются пакеты на все случаи жизни. Поэтому простыми и понятными оказались задачи по сборке из имеющихся пакетов того, что нужно именно мне, и по запуску всего этого на Read Write Serve. Для организации работы всего того, что нужно современному веб-проекту, не требуется прилагать чрезмерных усилий. Это — отправка электронной почты, работа с платёжными сервисами, обращение к базам данных, и всё остальное, подразумевающее работу с серверными API.
▍5. База данных MariaDB
Я пользуюсь сервером баз данных MariaDB. Это — форк MySQL, подвергнутый ребрендингу и освоенный опенсорс-сообществом. Когда мне нужно хранить неструктурированные JSON-данные, я пользуюсь PostgreSQL. Дело в том, что это позволяет мне выполнять запросы непосредственно по конкретным JSON-свойствам. Это немного похоже на MongoDB, но основано на привычном SQL-синтаксисе.
▍6. HTTP/2
Для организации связи между частями приложений я полагаюсь на возможности HTTP/2 с поддержкой постоянных соединений и с мультиплексированием потоков. Эти два дополнения к достойному уважения протоколу HTTP/1.1. изменили мой подход к формированию документов. Во-первых, исчезла проблема блокировки начала очереди. В результате пропала необходимость в спрайт-листах даже в том случае, если у меня имеются десятки маленьких изображений. Во-вторых, теперь не нужно оптимизировать JavaScript- и CSS-файлы, объединяя их в бандлы. После того, как соединение клиента и сервера установлено, все эти маленькие файлы без перебоев передаются по этому соединению.
▍7. HTML-шаблонизация с помощью Blue Phrase
Blue Phrase — это система шаблонизации, позволяющая в компактном виде точно описывать HTML-структуры. Для меня закончились времена нечитаемой мешанины из HTML-кода и несоответствий между открывающими и закрывающими тегами. В шаблонах я обычно использую лишь незначительное количество переменных (заголовок, описание, ключевые слова, SEO-данные, экран загрузки, дата и так далее) и размещаю их в шаблоне в декларативном стиле.
▍8. Написание кода страниц с помощью Read Write Doc
Когда я создаю новые страницы, я сосредоточен на том, что пытаюсь выразить, а не на их оформлении. Для решения этой задачи я пользуюсь Read Write Doc. Этот инструмент помогает мне заниматься делом, ни на что не отвлекаясь. Я пользуюсь им даже тогда, когда то, над чем работаю, планируется опубликовать на Medium (а там есть отличный онлайновый WYSIWYG-редактор). Я отношу себя к ветеранам веб-разработки, поэтому привык к моноширинным шрифтам, и к тому, чтобы мои руки были бы на клавиатуре, а не метались бы между клавиатурой и мышкой. В любом случае, если мне нужно увидеть то, над чем я работаю, с применением к нему CSS, я могу, с помощью простой комбинации клавиш, переключаться между режимами просмотра и редактирования.
▍9. Стандартные веб-компоненты
В области работы с веб-компонентами я придерживаюсь стандартов W3C. Это — теневая DOM, пользовательские элементы, HTML-тег <template>
, ECMAScript-модули. Это позволяет мне полностью включать всё, что мне нужно, в пакеты, которые я распространяю через NPM. Для меня самым большим преимуществом всего этого стал тот уровень изоляции, который предоставляет теневая DOM. Это позволило избавиться от проклятья CSS, от загрязнения пространств имён.
▍10. JavaScript для клиентских скриптов
Для написания клиентских скриптов я пользуюсь модульным объектно-ориентированным JavaScript-кодом. Я применяю новые возможности стандарта ECMAScript только тогда, когда их поддержка появляется в свежих релизах браузеров. То есть, включаю их в свой арсенал в тот момент, когда вижу, что на caniuse.com «зеленеют» все основные браузеры. Я избегаю полифиллов.
▍11. Стилизация с помощью CSS
CSS — это типографское оформление и макеты страниц. Типографика начинается с правильного подбора шрифтов. Самое главное для меня — хорошая читаемость текста. В последнее время я взял себе за правило хостить файлы используемых шрифтов на собственном сервере. Это ограждает мои ресурсы от возможности блокировки, вызванной ограниченной полосой пропускания некоего стороннего сервиса. Например, я пользуюсь примерно такими конструкциями:
<link href='/fonts/source-serif-pro-400-latin.woff2' rel=preload as=font crossorigin />
Дополнительное преимущество такого подхода заключается в том, что он полностью избавляет меня от проблемы, известной как FOUT — (flash of unstyled text, вспышка обычного шрифта).
▍12. Подготовка графических ресурсов с помощью GIMP и InkScape
И, наконец, для подготовки графических ресурсов я использую пару редакторов. Растровые PNG-изображения я готовлю с помощью GIMP, а векторные SVG-материалы — с помощью InkScape.
Технологии, которые потеряли былую привлекательность
Некоторые средства, которые раньше мне очень нравились, а также некоторые, которыми я увлекался лишь мимолётно, больше не входят в мой стек веб-технологий.
Технологии, которые стали фаворитами
Вот обзор тех уровней моего технологического стека, которыми я особенно впечатлён:
- JavaScript-модули. Модули отлично зарекомендовали себя в серверном JavaScript-коде. И я безмерно рад тому, что наконец могу использовать их и на стороне клиента.
- Объектно-ориентированный JavaScript. Вот пять золотых правил объектно-ориентированной JavaScript-разработки:
- Заменяйте анонимные объекты именованными классами.
- Объявляйте и инициализируйте все свойства объектов в конструкторах.
- Защищайте объекты от изменений сразу после создания.
- Объявляйте методы с неизменными сигнатурами.
- Привязывайте
this
к каждому коллбэку.
- Blue Phrase. Эта система позволяет мне пользоваться декларативным подходом при создании шаблонов и при подготовке различных материалов. Она превращает написание качественного HTML-кода в сплошное удовольствие.
Итоги
Раньше стеки веб-технологий вполне могли включать в себя всего четыре элемента. Но в современных условиях никого не удивишь стеком, состоящим из двенадцати уровней. Не хочу выглядеть как человек, озвучивающий общеизвестные истины, но, всё же, завершая этот материал, скажу о том, что у каждого разработчика может быть собственный стек. И мне было бы крайне интересно узнать о том, что другие назовут своим «идеальным стеком веб-технологий 2020 года».
Уважаемые читатели! А какой он — ваш стек веб-технологий 2020 года?
Дисклеймер от переводчика: Blue Phrase, Read Write Serve и Read Write Doc — технологии, разработанные автором данной статьи. Скачивание и установка — на ваш риск.
Стек
— Справочник по C ++
Шаблон класса
<стопка>
template> class stack;
Стек LIFO
Стеки — это тип адаптера контейнера, специально разработанный для работы в контексте LIFO (последним пришел — первым вышел), когда элементы вставляются и извлекаются только с одного конца контейнера.
стек s реализованы как адаптеры контейнера , которые представляют собой классы, которые используют инкапсулированный объект определенного класса контейнера в качестве своего базового контейнера , предоставляя определенный набор функций-членов для доступа к его элементам.Элементы выдвигаются, / выдвигаются, из «задней» конкретного контейнера, который известен как верхний стопки.
Базовый контейнер может быть любым из стандартных шаблонов контейнерных классов или каким-либо другим специально разработанным контейнерным классом. Контейнер должен поддерживать следующие операции:
- пустой
- размер
- задний
- push_back
- pop_back
Стандартные классы контейнера vector , deque и list удовлетворяют этим требованиям.По умолчанию, если класс контейнера не указан для конкретного экземпляра класса stack , используется стандартный контейнер deque .
Параметры шаблона
- т
- Тип элементов.
Псевдоним типа члена stack :: value_type . - Контейнер
- Тип внутреннего контейнера , лежащего в основе объекта , в котором хранятся элементы.
Его value_type должно быть T .
Псевдоним типа члена stack :: container_type .
Типы элементов
тип элемента | определение | примечания |
---|---|---|
value_type | Первый параметр шаблона ( T ) | Тип элементов |
container_type | Второй параметр шаблона ( Контейнер ) | Тип нижележащего контейнера |
size_type | целочисленный тип без знака | обычно совпадает с size_t |
тип элемента | определение | примечания |
---|---|---|
value_type | Первый параметр шаблона ( T ) | Тип элементов |
container_type | Второй параметр шаблона ( Контейнер ) | Тип нижележащего контейнера |
ссылка | container_type :: reference | обычно, value_type & |
const_reference | container_type :: const_reference | обычно значение, |
size_type | целочисленный тип без знака | обычно, то же, что и size_t |
Функции-члены
- (конструктор)
- Стек конструкции (общедоступная функция-член
)
- пусто
- Проверить, пуст ли контейнер (общедоступная функция-член
)
- размер
- Размер возврата (общедоступная функция-член
)
- вверху
- Доступ к следующему элементу (общедоступная функция-член
)
- push
- Вставить элемент (общедоступная функция-член
)
- emplace
- Построить и вставить элемент (общедоступная функция-член
)
- pop
- Удалить верхний элемент (общедоступная функция-член
)
- swap
- Swap содержимое (общедоступная функция-член
)
Перегрузки функций, не являющихся членами
- операторы отношения
- операторы отношения для стека (функция
)
- своп (стек)
- Обмен содержимым стеков (общедоступная функция-член
)
Специализации класса, не являющиеся членами
- uses_allocator
- Использует распределитель для стека (шаблон класса
)
.
C # Стек
- Подписывайтесь на нас
- Питон
- ASP.NET Core
- MVC
- IoC
- Веб-API
- C #
- TypeScript
- Node.js
- Больше
✕
. Учебники .NET
- ASP.NET Core
- ASP.NET MVC
- IoC
- веб-API
- C #
- LINQ
Учебники по скриптам
- TypeScript
- AngularJS 1
- Узел.js
- D3.js
- jQuery
- JavaScript
Другие учебные пособия
- Python
- Sass
- Https
Тесты навыков
- ASP.NET Core
- ASP.NET MVC
- LINQ
- C #
- веб-API
- IoC
- TypeScript
- AngularJS
- Node.js
- jQuery
- JavaScript
- Статьи
- Тесты
- Напишите нам
- Учебники по C #
- C # — Начало работы
- C # — История версий
- C # — Первая программа
- C # — Ключевые слова
- C # — Класс
- C # — переменная
- C # — неявно типизированная переменная
- C # — Типы данных
- Числа
- Струны
- DateTime
- C # — Типы значений и ссылок
- C # — анонимный тип
- C # — динамические типы
- C # — Интерфейс
- C # — Операторы
- C # — операторы if else
- C # — Тернарный оператор?:
- C # — переключатель
- C # — цикл For
- C # — цикл пока
- C # — цикл Do-while
- C # — Структура
- C # — Enum
- C # — StringBuilder
- C # — частичный класс
- C # — статический
- C # — Массив
- Многомерный массив
- Зубчатый массив
- C # — Индексатор
- C # — Дженерики
- Общие ограничения
- C # — Коллекции
- ArrayList
- Список
- SortedList
- Словарь
- Хеш-таблица
- Стек
- Очередь
- C # — Кортеж
- C # — ValueTuple
- C # — встроенные исключения
- Обработка исключений
- бросить
- Пользовательское исключение
- C # — Делегаты
- C # — События
- C # — анонимный метод
- C # — Типы, допускающие значение NULL
- C # — Ковариация
- C # — Func
- C # — Действие
.
std :: stack — cppreference.com
шаблон < класс T, > стек классов; | ||
Класс std :: stack
— это контейнерный адаптер, который дает программисту функциональность стека, в частности структуру данных LIFO (последний вошел, первый вышел).
Шаблон класса действует как оболочка для базового контейнера — предоставляется только определенный набор функций.Стек выталкивает и выталкивает элемент из задней части нижележащего контейнера, известной как вершина стека.
[править] Параметры шаблона
т | — | Тип хранимых элементов. Поведение не определено, если T не того же типа, что и Container :: value_type . (начиная с C ++ 17) |
Контейнер | — | Тип базового контейнера, используемого для хранения элементов.Контейнер должен удовлетворять требованиям SequenceContainer. Кроме того, он должен обеспечивать следующие функции с обычной семантикой:
Стандартные контейнеры std :: vector, std :: deque и std :: list удовлетворяют этим требованиям. По умолчанию, если для конкретного экземпляра класса стека не указан класс контейнера, используется стандартный контейнер std :: deque. |
[править] Типы элементов
Тип элемента | Определение |
тип_контейнера | Контейнер [править] |
тип_значения | Контейнер :: value_type [править] |
типоразмер | Контейнер :: size_type [править] |
ссылка | Контейнер :: ссылка [править] |
const_reference | Контейнер :: const_reference [править] |
[править] Функции-члены
создает стек (общедоступная функция-член) [править] | |
разрушает стек (общедоступная функция-член) [править] | |
присваивает значения адаптеру контейнера (общедоступная функция-член) [править] | |
Доступ к элементу | |
обращается к верхнему элементу (общедоступная функция-член) [править] | |
Вместимость | |
проверяет, пуст ли базовый контейнер. (общедоступная функция-член) [править] | |
возвращает количество элементов (общедоступная функция-член) [править] | |
Модификаторы | |
вставляет элемент вверху (общедоступная функция-член) [править] | |
создает элемент на месте наверху (общедоступная функция-член) [править] | |
удаляет верхний элемент (общедоступная функция-член) [править] | |
меняет местами содержимое (общедоступная функция-член) [править] | |
Объекты-элементы | |
базовый контейнер (защищенный объект-член) [править] |
[править] Функции, не являющиеся членами
[править] Вспомогательные классы
[править] Руководства по выводам (начиная с C ++ 17)
.Программа стека
на C ++ — Qaru
Переполнение стека
- Около
Продукты
- Для команд
Переполнение стека
Общественные вопросы и ответыПереполнение стека для команд
Где разработчики и технологи делятся частными знаниями с коллегамиВакансии
Программирование и связанные с ним технические возможности карьерного ростаТалант
Нанимайте технических специалистов и создавайте свой бренд работодателяРеклама
Обратитесь к разработчикам и технологам со всего мира- О компании
Загрузка…
- Авторизоваться
зарегистрироваться текущее сообщество
.