Как символы хранятся в памяти компьютера?

Обновлено: 21.11.2024

Почти все программы выполняют множество манипуляций с текстовыми строками. Текстовые строки состоят из массивов символов. Вероятно, первой написанной вами программой была программа «Hello world». Если вы написали это на C, вы использовали оператор вроде:

При переводе любого из этих операторов в машинный код компилятор должен сделать две вещи:

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

генерировать машинные инструкции для вывода символов на экран.

Начнем с рассмотрения того, как отдельный символ хранится в памяти. Существует много кодов для представления символов, но наиболее распространенным является Американский стандартный код для обмена информацией (ASCII — произносится как «аск'э»). Он использует семь битов для представления каждого символа. В таблице 2.13.1 показаны битовые комбинации для каждого символа в шестнадцатеричном формате. Если вы находитесь за своим компьютером, вы можете создать эту таблицу, введя команду man ascii .

Таблица 2.13.1. Код ASCII для представления символов. Битовые комбинации (bit pat.) показаны в шестнадцатеричном формате.

Это не та таблица, которую можно запомнить. Тем не менее, вам следует ознакомиться с некоторыми его общими характеристиками. В частности, обратите внимание, что числовые символы от 0 до 9 находятся в непрерывной последовательности в коде \(\hex\)–\(\hex\text<.>\). То же самое верно и для строчных буквенных символов. , a – z и прописных букв, A – Z . Обратите внимание, что строчные буквы алфавита численно выше, чем прописные.

Коды в левой колонке таблицы 2.13.1, \(\hex\)–\(\hex\text\) определяют . Код ASCII был разработан в 1960-х годах для передачи данных от отправителя к получателю. Если вы прочтете некоторые названия управляющих символов, то сможете представить, как их можно использовать для управления «диалогом» между отправителем и получателем. Их можно сгенерировать на клавиатуре, удерживая нажатой клавишу управления и одновременно нажимая буквенную клавишу. Например, сочетание клавиш ctrl-d создает символ EOT (конец передачи).

Коды ASCII обычно хранятся в самых правых семи битах восьмибитного байта. Восьмой бит (старший бит) называется битом четности. Его можно использовать для обнаружения ошибок следующим образом. Отправитель и получатель заранее договорятся, использовать ли четность или нечетность. Четность означает, что в каждом символе всегда передается четное количество единиц; нечетная четность означает, что передается нечетное количество единиц. Перед передачей символа в коде ASCII отправитель корректировал восьмой бит так, чтобы общее количество единиц соответствовало четному или нечетному соглашению. Когда код был получен, приемник считал единицы в каждом восьмибитном байте. Если сумма не соответствовала соглашению, получатель знал, что один из битов в байте был принят неправильно. Конечно, если бы два бита были получены неправильно, ошибка осталась бы незамеченной, но вероятность этой двойной ошибки удивительно мала. Современные системы связи намного надежнее, а контроль четности редко используется при отправке отдельных байтов.

В некоторых средах старший бит используется для предоставления кода для специальных символов. Небольшое размышление покажет вам, что даже все восемь бит не будут поддерживать все языки, например, греческий, русский, китайский. Стандарт символов Unicode был впервые представлен в 1987 году и развивался с годами. Он включает дополнительные байты, чтобы он мог обрабатывать другие алфавиты. Юникод обратно совместим с ASCII. В этой книге мы будем использовать только ASCII.

Компьютерную систему, использующую видеосистему ASCII, можно запрограммировать на отправку байта на экран. Видеосистема интерпретирует битовую комбинацию как код ASCII (из таблицы 2.13.1) и отображает соответствующий символ на экране.

Возвращаясь к текстовой строке "Hello world\n", компилятор сохранит ее как постоянный массив символов. Должен быть способ указать длину этого массива. В a это достигается использованием контрольного символа NUL в конце строки. Таким образом, компилятор должен выделить для этой строки тринадцать байтов. Пример того, как эта строка хранится в памяти, показан на рис. 2.13.2. Обратите внимание, что C использует символ LF как один символ новой строки, несмотря на то, что синтаксис C требует, чтобы программист писал два символа, « \n ». Показанная область памяти включает три байта, следующие непосредственно за текстовой строкой.

Таблица 2.13.2. Текстовая строка, хранящаяся в памяти компилятором C, включая три «мусорных» байта после строки. Значения отображаются в шестнадцатеричном формате. Другая компиляция может поместить строку в другое место в памяти.

< td >\(\hex:\) < td >\(\hex\)
Адрес Содержание
\(\hex:\) \(\hex \)
\(\hex:\) \(\hex\)
\(\hex:\) \(\hex\)
\(\hex:\) \( \hex\)
\(\hex:\) \(\hex\)
\(\hex\)
\(\hex:\) \(\hex\)
\(\hex:\) \(\hex\)
\(\hex:\) \(\hex\)
\(\hex:\)
\(\hex:\) \(\hex\)
\(\hex:\) \(\hex\)
\(\hex:\) \(\hex\)
\(\hex:\) \(\hex\)
\(\hex:\) \(\hex\)
\(\hex:\)< /td> \(\hex\)

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

Хранятся ли символы в памяти в соответствии с их кодами ascii? Скажем, 'A' имеет код anscii 65. Значит, он хранится в памяти так же, как целое число 65?

Если да, то как машина различает символ и целое число?

Если символы хранятся в виде кодов ascii, код ascii представляет собой целое число. Целое число должно занимать как минимум 2 байта, почему символ занимает только 1 байт?

Последний вопрос касается целых чисел на разных архитектурах. Если на 16-битной машине 1 хранится как 000. 0001, то на 32-битной машине 1 по-прежнему хранится так же, просто добавляется 0 впереди?

2 ответа 2

Сохраняются ли символы в памяти в соответствии с их кодами ascii? Скажем, 'A' имеет код anscii 65. Значит, он хранится в памяти так же, как и целое число 65?

Да, но char в C — это один байт, а int зависит от архитектуры компьютера.

Если да, то как машина различает символ и целое число?

Машинному коду все равно, что представляют собой байты в памяти. Задача компилятора — преобразовать ваш код в машинные инструкции, которые делают то, что делает ваша программа.

Если символы хранятся в виде кодов ascii, код ascii является целым числом. Целое число должно занимать как минимум 2 байта, почему символ занимает только 1 байт?

ASCII может уместиться в одном байте (размером с char ). Работа с текстом, отличным от ASCII, в C более сложна. Существует wchar_t, который не является переносимым, и многие люди считают его неработающим. C11 вводит char16_t и char32_t , которые можно использовать для UTF-16 и UTF-32 соответственно.

Последний вопрос касается целых чисел на разных архитектурах. На 16-битной машине, если 1 хранится как 000. 0001, то на 32-битной машине 1 по-прежнему сохраняется таким же образом, просто добавляя 0 впереди?

В основном это правильно, но это также зависит от порядка байтов в архитектуре.

Нет абсолютно никакой разницы между char a = 'A' и char a = 65 . Если вы спрашиваете о int a = 65 , то да, у него есть лишние 0.

@imgx64: есть разница между char a = 'A' и char a = 65 . Первый инициализирует a значением «A» в наборе символов выполнения, который может быть или не быть ASCII. Последний инициализирует a значением 65. Если используется ASCII, они одинаковы, но есть реализации C, которые не используют ASCII.

@imgx64 Я попытался распечатать sizeof('A'), получилось 4; но sizeof(char) действительно равен 1. Это потому, что 'A' по умолчанию хранится как int в C?

Да, символы ASCII сохраняются по их значению. Но сохранение «A» (65 = 0x41) может отличаться от хранения самого 65, и то, как это делается, зависит от архитектуры вашей машины. char может храниться с одним байтом, а int будет иметь как минимум 2 байта (чаще 4 байта на современных машинах), поэтому они могут храниться по-разному.

Это не так. У нас могла бы быть память, равная 0x41. Единственный способ различать «A» и 65 основан на том, как вы объявили его компилятору. Другими словами, если вы объявили переменную как int, она будет рассматриваться как int.

Существует так мало значений ASCII, что вы можете представить все возможности с помощью менее чем 8 бит. Таким образом, использование 16 бит для представления этого было бы пустой тратой памяти. В современных системах это уже не такая большая проблема, но в системах с ограниченным объемом памяти вы можете использовать этот лишний байт для чего-то другого, а не тратить место впустую.

Более или менее, да. 1 всегда будет храниться как 0000. 1, так что общее количество двоичных цифр, равное этому, заполняет пространство для целого числа. Таким образом, в 8-битной системе это будет 00000000 и 00000001 в двух словах, в 16-битной системе это будет 000000000000001 в одном слове.

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

Строки

В компьютерной терминологии фрагмент текста чаще называют строкой. В большей степени это помогает избежать каких-либо предположений о содержании, потому что строки могут фактически включать любые символы, не только буквы AZ (как в нижнем, так и в верхнем регистре), но и цифры 0-9, знаки препинания, пробелы, табуляции, разрывы строк и другие «специальные символы». Строка может состоять из одного символа или длиннее, чем «Война и мир». В конечном счете, однако, каждую строку можно разбить на отдельные символы, которые, в свою очередь, состоят из байтов, которые сами по себе являются не чем иным, как восемью битами комбинаций 1 или 0. Но как это работает на практике?

Наборы символов
Юникод

Набор символов ASCII имеет длинную родословную, восходящую к 1960-м годам, первоначально он был получен из поиска, используемого для отправки телеграфных сообщений. Однако он ограничен, поскольку может поддерживать не более 128 различных символов. Это связано с тем, что ASCII использует только 7 бит из 8 бит, доступных в одном байте. Некоторые более поздние версии ASCII, такие как ANSI, расширили исходный набор символов ASCII до 256 различных символов (за счет использования 8-го бита), поскольку, как мы узнали из чисел, один байт может использоваться для хранения до 256 различных чисел. Это дает больше возможностей для включения букв с диакритическими знаками (например, ö, ç, ù и т. д.) для поддержки текста на большинстве европейских языков. Однако таким языкам, как китайский, в котором используются идеограммы, а не буквы, требуется много тысяч различных символов, гораздо больше, чем 256. В этом отношении бедный старый ASCII — расширенный или нет — нам не поможет. Однако радость байтов заключается в том, что их совокупная емкость возрастает экспоненциально: если мы используем два байта для хранения каждого символа, мы можем иметь 65 536 различных символов (подробнее о том, почему это так, см. здесь). Наборы символов Unicode, из которых UTF-8 наиболее часто используется в Интернете, делают именно это; на самом деле UTF-8 использует до четырех байтов для одного символа. Конечно, использование дополнительных байтов увеличивает размер хранилища, необходимого для текстовых данных, но UTF-8 аккуратно обходит эту проблему, используя так называемую кодировку символов «переменной ширины». То есть для первых 128 символов — таких же, как в ASCII — используется всего один байт. Для более высоких символов он расширяется на один, два или три дополнительных байта в зависимости от требуемого символа. Это позволяет избежать необходимости использовать четыре байта для каждого символа, сохраняя при этом гибкость поддержки гораздо более широкого набора символов, чем может ASCII.

Пример работы

Используя Блокнот Windows, вы действительно можете увидеть разницу в том, как строковые данные кодируются и хранятся на компьютере. Если вы создадите пустой текстовый файл в Блокноте, вы увидите, что он имеет размер 0 байт:

Это связано с тем, что файл, даже с указанным именем, представляет собой не что иное, как указатель на место хранения до тех пор, пока в файле не появится содержимое. Пока файл остается пустым, это всего лишь указатель на место, содержащее 0 байтов данных. Если теперь открыть файл, ввести одну букву и сохранить его, размер файла увеличится ровно до 1 байта. Однако вам придется проверить свойства файла, чтобы увидеть это, потому что проводник Windows округляет до ближайшего килобайта!

В шестнадцатеричном формате файл будет выглядеть так:

Теперь сохраните файл под другим именем, но в диалоговом окне "Сохранить как" выберите кодировку "UTF-8":

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

Что занимает дополнительные 3 байта? Ответом является то, что называется «метка порядка байтов» (BOM), которая представляет собой стандартный трехбайтовый фрагмент информации, сообщающий любой программе, которая открывает этот файл, что он был закодирован в UTF-8. Те же три байта (в шестнадцатеричном формате: &EF &BB &BF) появятся в начале любого текстового файла, закодированного в UTF-8 такими программами, как Блокнот Windows. Не является требованием, чтобы что-то, закодированное в UTF-8, имело этот знак порядка следования байтов в начале, и многие программы пропускают его, включая многие веб-приложения.Однако для программ, которые ожидают его наличия, если его там нет, программа предположит (возможно, неправильно!), что текстовая информация была закодирована в ASCII, потому что ASCII никогда не имеет метки порядка байтов.

Если сейчас вы добавите в файл еще одну букву и продолжите сохранять его в кодировке UTF-8, он увеличится еще на один байт:

Это связано с тем, что, как мы обсуждали выше, UTF-8 представляет собой кодировку символов переменной ширины, и используемый здесь дополнительный символ (буква 'A') не выходит за пределы первых 128 символов. . Мы можем проверить эту теорию дальше, добавив в файл третий символ, на этот раз из кириллицы. Символ Ю (заглавная буква «Ю») не является частью первых 128 символов, но находится в пределах первых 65 536 символов, поэтому для его хранения потребуется всего два байта. Конечно же, когда мы сейчас проверяем свойства файла, файл вырос с 5 байт до 7 байтов:

В шестнадцатеричном формате вы можете видеть, что один дополнительный символ дал нам еще два байта (&D0 и &AE):

Что произойдет, если мы сейчас сохраним этот же файл, используя ANSI? Если вы попробуете это, вы получите предупреждение от Блокнота о том, что файл содержит символы в формате Unicode, которые будут потеряны, если вы продолжите. В любом случае нажмите «ОК», а затем посмотрите свойства файла… размер файла уменьшился до 3 байт!

В шестнадцатеричном формате файл теперь выглядит так:

Это связано с тем, что первые три байта метки порядка следования байтов были удалены (помните, что ни ASCII, ни ANSI не используют метки порядка следования байтов), а дополнительный байт (&AE) для нашей кириллической буквы также был утерян. На самом деле все еще хуже: испорчен даже первый байт (&D0) двухбайтного символа кириллицы. Если вы откроете файл, то увидите, что вместо Ю у нас стоит вопросительный знак (в шестнадцатеричном формате: &3F). Это способ Блокнота показать, что он больше не знает, каким должен быть символ, поскольку дополнительный байт данных был потерян.

Кстати, если вы хотите посмотреть, как файлы на самом деле отображаются на компьютере как в шестнадцатеричном, так и в двоичном виде, может быть интересно (при заданном значении «весело»…) использовать шестнадцатеричный редактор. Эти инструменты позволяют просматривать файлы в их шестнадцатеричном представлении. HxD — хороший бесплатный шестнадцатеричный редактор, который мне нравится использовать. Его можно скачать здесь.

Мы надеемся, что это демонстрирует как наборы символов работают на практике, так и их важность для компьютера при хранении текстовых данных. В следующем посте мы рассмотрим даты…

как хранятся символы? Используется ли 16/32/64-битный ASCII??

Что делать, если у меня есть данные шириной 8 бит? можно ли хранить много символов в одной позиции?

например, для 32 бит, если используются только 8 бит, 24 бита не используются?

позиция памяти/диска-> 0000000 00000000 0000000 xxxxxxx

или направление памяти/диска 16/32/64 по-прежнему указывает на 8-битные слова вместо 16/32/64-битных?

Значит, 8 бит все еще живы и работают? кажется ДА

ИЗМЕНИТЬ

Забывая об ASCII, я хотел бы знать, указывает ли один адрес (в памяти/диске) на один 8-битный байт на 8/16/32/64-битной платформе

5 ответов 5

Если длина превышает 8 бит, символ не является ASCII по определению. Цифры остаются цифрами.

Байты остаются байтами. Компьютеры с более широкими путями данных просто захватывают больше их одновременно. 32-разрядная система изначально будет обрабатывать 4 байта за раз, а 64-разрядный компьютер будет использовать 8 байтов.

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

+1 за упоминание символов, отличных от ASCII. Сегодня Unicode очень распространен, и, например, символ UTF-8 может занимать от 1 до 4 байтов.

"Управление 4 байтами за раз" мне непонятно. Забыв об ASCII, я хотел бы знать, всегда ли один адрес (в памяти/диске) указывает на один байт, я задам это в вопросе

>> если один адрес (в памяти/на диске) всегда указывает на один байт. Зависит от типа данных. Адресуются отдельные байты — представьте себе тип CHAR, но указатели в 32-битной системе имеют размер 4 байта и так далее. Так что да, это может произойти, но «это зависит», когда вы говорите о конкретном адресе (предположительно, адрес принадлежит чему-то в программе).

поэтому ответ НЕТ, это не всегда указывает на один байт, потому что есть некоторое оборудование, которое имеет более широкую минимальную ширину данных, но в аппаратном обеспечении ПК минимальный доступ составляет 8 бит

Размер адресного пространства указывается в байтах. Например, вы покупаете компьютер с 4 ГБ ОЗУ или 3 ТБ на диске. Таким образом, адреса также указывают на один байт.

При адресации более 8 бит вы также ссылаетесь на последующие байты. Предположим, у вас есть указатель на адрес 104. Если вы загружаете в 64-битный регистр, вы получаете байты со 104 по 111. Если вы сохраняете, вы перезаписываете эти адреса.

Ваш основной вопрос о том, как данные символов хранятся в памяти, как в ОЗУ, так и на диске? Как правило, данные в рабочей памяти занимают больше места, но с ними легче работать; а на диске он более компактный, с какой-то кодировкой символов. Но есть много вариаций, плюсов и минусов для них.

Например, символы нередко занимают в памяти два байта каждый, но при сохранении на диске занимают от одного до четырех байтов. Например "Азбука" в памяти: 65 00 66 00 67 00; на диске: 65 66 67. Для специального символа, известного как метка порядка байтов, в памяти: 255 254; на диске: 239 187 191. Это символы Unicode, сохраненные на диске в кодировке UTF-8.

(Технически говоря, ASCII является 7-битным; он определяет только 128 символов. Юникод — это 16-битный надмножество ASCII.)

ок, так как размер указан в байтах, то 8 бит еще живы =) и будут с нами еще долго

Для любого потока текста кодировка символов должна быть каким-то образом выражена либо в потоке (в начале), либо вне его (например, в свойстве файла); в противном случае предполагается некоторое значение по умолчанию, часто основанное на текущей локали (стране и языке). Присутствие этого специального символа Byte Order Mark, о котором я упоминал, поможет Блокноту угадать. Если применяется неправильная кодировка символов, вы можете получить неправильные символы, от нескольких до всех.

Когда разрабатывались 16-разрядные процессоры, они сохраняли возможность одновременной адресации 8 битов для обеспечения скорости и совместимости. Существуют различные компоненты ЦП, которые могут иметь «разрядность», и ширина регистра является одним из них. Но почти все 16-битные или более мощные ЦП имеют инструкторов для доступа к старшим или младшим 8 битам регистра. Таким образом, только то, что ЦП является таким многобитным, не означает, что он должен обращаться к памяти или регистрам фрагментами такого размера.

Чтобы ответить на ваши вопросы:

16/32/64 направление памяти/диска по-прежнему указывает на 8-битные слова вместо 16/32/64-битных слов? Да. 32-разрядный ЦП, загружающий 32 бита в регистр из заданного места в памяти, возьмет 4 байта из DRAM и поместит их в регистр.

Все немного сложнее, чем простые ответы, данные до сих пор.

Есть два аспекта: машина и запоминающее устройство.

Это зависит от аппаратной архитектуры.

На ПК адресация осуществляется по байтам, и вы можете обращаться к байту (8 бит), слову (16 бит), двойному слову (32 бита) и квадрослову (64 бита).

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

Итак, на этом TMS320C40 компилятор C имеет собственный тип char, который составляет 32 бита!

(при программировании на языке C НИ В КОЕМ СЛУЧАЕ НЕ ПРЕДПОЛАГАЕТСЯ, что char имеет длину 8 бит. Прочтите руководство по компилятору, особенно если вы программируете встраиваемые системы).

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

Адрес X: байт 0, байт 1, байт 2, байт 3

Адрес X+4: байт 4, байт 5, байт 6, байт 7

Адрес X: байт 3, байт 2, байт 1, байт 0

Адрес X+4: Байт 7, Байт 6, Байт 5, Байт 4

(И это становится еще более сложным, потому что биты в байте также имеют порядок следования байтов.)

В ГОЛОВНОМ ВИДЕ такого рода вещи беспокоят только разработчиков аппаратного обеспечения. Но если вам нужно писать драйверы устройств и вещи, которые взаимодействуют с оборудованием через регистры, отображаемые в память, это становится большой проблемой.

Достаточно простого примера:

Дамп блока памяти по адресу X может представлять собой поток байтов:

01 02 03 04 05 06 07 08

НО вывод того же блока с того же адреса и представление в виде 16-битных (шестнадцатеричных) целых чисел может выглядеть следующим образом:

0201 0403 0605 0807

И повторный вывод дампа с того же адреса, поскольку 32-битные целые числа в шестнадцатеричном виде могут быть представлены как:

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

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

К счастью, здесь жизнь становится проще.

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

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

Все вышеперечисленное относится к низкоуровневым вещам — байтам, словам и т. д.

Программы используют это по-разному. Так, например, вы получите СИМВОЛЫ, представленные байтами, если они удачно вписываются в простой ASCII (или даже EBCDIC для тех, у кого долгая память). Современные системы символов Unicode могут использовать широкие символы (обычно это 16 бит), но существует множество систем кодирования для Unicode. Страница Википедии, посвященная Unicode, весьма поучительна.

Соглашение в C о том, что CHARACTER = BYTE, в наши дни вводит в заблуждение и ошибочно. Лучше всего, чтобы "char" был синонимом "byte" - если только ваша машина/компилятор не говорит иначе (см. выше). ХОРОШИЕ программы на C обычно определяют набор предпочтительных типов, таких как "UINT8" - 8-битное целое без знака, "SINT8" - 8-битное целое со знаком и так далее, так что написанная программа становится настолько независимой, насколько это возможно, от особенностей языка. конкретный компилятор и базовое оборудование.

На конкретный вопрос: как хранятся символы? Ответ: это зависит. Часто символы ascii, которые помещаются в байт, сохраняются как байт. Широкие символы часто хранятся в виде 16-битных слов. Но Юникод может использовать расширенные символы или одну из систем кодирования, и в этом случае символы могут занимать от 1 до 4 байтов, в зависимости от символа.

Читайте также: