Сколько памяти занимает string c
- Целые типы: sbyte , byte , short , ushort , int , uint , long , ulong .
- Типы, в которых сохраняются данные с плавающей запятой (соответствуют вещественному типу): float , double , decimal .
- Символьный тип: char .
- Логический тип: bool .
- Строчный тип: string .
2. Какие особенности использования целочисленных типов данных?
sbyte , byte , short , ushort , int , uint , long , ulong .
Данные таких типов представляют значения из множества целых чисел. Например: -200 , 8938 , 1134448348 .
Символы u перед названиями некоторых типов есть сокращением от слова unsigned . Это означает, что значение переменных этих типов есть без знака, то есть сохранять отрицательные числа в переменных этих типов нельзя.
Переменные типы sbyte и byte занимают 1 байт в памяти компьютера. Переменные типа short и ushort – 2 байта. Переменные типа int и uint – 4 байта. Переменные типа long и ulong – 8 байт.
3. Как в программе описать переменную с именем d целого типа int ?
После такого описания под переменную с именем d будет выделено 4 байта памяти компьютера.
4. Как в переменную d целого типа ulong занести число 398 ?
Ответ 1. Присваивание значения переменной после ее описания.
Ответ 2. Присваивание значения переменной во время ее описания (начальная инициализация).
5. Как программно определить размер переменной заданного типа?
Чтобы определить размер переменной используется операция sizeof() :
Таким способом можно определить размер в байтах переменной любого базового типа.
6. Какие особенности типов данных с плавающей запятой (вещественных типов)?
Эти типы позволяют сохранять числа с плавающей запятой, например:
По сравнению с типом double , тип decimal не решает ошибок округления но минимизирует их. Ошибки округления возникают в случае, когда происходит операция деления чисел, результат которой дает дробную часть в периоде
При умножении этого числа 0.33333(3) на 3 уже не будет получено первоначальное число 1.0. Таким образом происходит потеря точности. Тип decimal предназначен для уменьшения этих потерь точности за счет увеличения количества разрядов в дробной части числа.
7. Как описать переменную соответствующего вещественного типа (с плавающей запятой)?
Пример описания переменных типа float , double , decimal :
Здесь описываются три переменные с именами x , y , z . Под переменную x выделяется 4 байта памяти, под переменную y выделяется 8 байт, под переменную z выделяется 16 байт.
8. Как программно занести значения в переменную соответствующего вещественного типа?
Пример внесения числовых данных в переменные разных вещественных типов:
В данном коде, при внесении данных в переменную f , происходит переведение числового значения 9030.939 в тип float . Так же происходит переведение числа для переменной z в тип decimal . Такое преобразование есть необходимым для вещественных типов float и decimal , так как все числовые значения являются типа double .
9. Как определить сколько байт памяти занимает одна переменная типа float , double или decimal ?
Чтобы определить это, нужно написать следующий программный код:
10. Как перевести переменную типа float в тип int ?
Для этого используется операция приведения типов. В скобках нужно указать название типа к которому осуществляется приведение.
При использовании операций приведения типов, нужно учитывать ограничения, которые накладываются на типы, которые занимают меньше места в памяти компьютера.
Например, переменная типа short может представлять меньший диапазон чисел, чем переменные типа float , double . В следующем листинге происходит переполнение значения в переменной типа short :
11. Как перевести переменную из типа int в тип double ?
Пример приведения из int в double :
12. Какие есть символьные типы данных?
Ответ: char , string
Тип string представляет последовательность символов.
Переменные типа char могут получать значение одного символа. Значение переменной типа char берется в одинарные кавычки, например:
Переменная типа char представляет собой одиночный символ Unicode . Такая переменная занимает в памяти компьютера 2 байта.
Переменные типа string – это строки символов, которые взяты в двойные кавычки, например:
13. Какие особенности использования данных типа char в программе?
Данные типа char представляют символьное значение кода, введенного с клавиатуры. Код символа представляет собой целое число.
Фрагмент кода, в котором вычисляется код символа:
14. Как в программе по коду символа получить его символьное представление?
Фрагмент программного кода, который переводит код (целое число) в символ (тип char ):
15. Какие особенности использования переменных типа string ?
Переменные типа string представляют собой строки символов. Максимальная длина строки не ограничена.
Пример описания переменной типа string с именем s1 .
Пример внесения строки в переменные типа string :
К переменным типа string можно выполнять различные операции. Подробное описание наиболее распространенных операций с переменными типа string описан здесь.
16. Какие особенности использования переменной логического типа bool ?
Пример использования переменной типа bool :
17. Каким образом осуществляется начальная инициализация переменных различных типов?
18. Каким образом определить максимально допустимое (минимально допустимое) значение переменной определенного типа?
Примеры определения предельных значений переменных разных типов.
Для переменных типа int :
Для переменных типа ulong :
Для переменных типа float :
19. Пример, демонстрирующий отличие между типами double и decimal
В нижеследующем примере демонстрируется отличие в округлении между типами double и decimal .
При делении числа 1.0 на число 3.0 для обоих типов происходит потеря точности, что является естественным. При умножении полученного результата на 3.0 происходит следующее:
При создании строки не помешало бы указать её длину и ёмкость (или хотя бы знать эти параметры).
Длина std::string
size_type string::length() const
size_type string::size() const
Обе эти функции возвращают текущее количество символов, которые содержит строка, исключая нуль-терминатор. Например:
Хотя также можно использовать функцию length() для определения того, содержит ли строка какие-либо символы или нет, эффективнее использовать функцию empty():
bool string::empty() const — эта функция возвращает true , если в строке нет символов, и false — в противном случае.
std :: cout << ( sString1 . empty ( ) ? "true" : "false" ) << std :: endl ; std :: cout << ( sString2 . empty ( ) ? "true" : "false" ) << std :: endl ;Есть еще одна функция, связанная с длиной строки, которую вы, вероятно, никогда не будете использовать, но мы все равно её рассмотрим:
size_type string::max_size() const — эта функция возвращает максимальное количество символов, которое может хранить строка. Это значение может варьироваться в зависимости от операционной системы и архитектуры операционной системы.
Ёмкость std::string
size_type string::capacity() const — эта функция возвращает количество символов, которое может хранить строка без дополнительного перераспределения/перевыделения памяти.
std :: cout << "Capacity: " << sString . capacity ( ) << std :: endl ;Length: 10
Capacity: 15
Примечание: Запускать эту и следующие программы следует в полноценных IDE, а не в веб-компиляторах.
Обратите внимание, ёмкость строки больше её длины! Хотя длина нашей строки равна 10, памяти для неё выделено аж на 15 символов! Почему так?
Здесь важно понимать, что, если пользователь захочет поместить в строку больше символов, чем она может вместить, строка будет перераспределена и, соответственно, ёмкость будет больше. Например, если строка имеет длину и ёмкость равную 10, то добавление новых символов в строку приведет к её перераспределению. Делая ёмкость строки больше её длины, мы предоставляем пользователю некоторое буферное пространство для расширения строки (добавление новых символов).
Но в перераспределении есть также несколько нюансов:
Во-первых, это сравнительно ресурсозатратно. Сначала должна быть выделена новая память. Затем каждый символ строки копируется в новую память. Если строка большая, то тратится много времени. Наконец, старая память должна быть удалена/освобождена. Если вы делаете много перераспределений, то этот процесс может значительно снизить производительность вашей программы.
Во-вторых, всякий раз, когда строка перераспределяется, её содержимое получает новый адрес памяти. Это означает, что все текущие ссылки, указатели и итераторы строки становятся недействительными!
Обратите внимание, не всегда строки создаются с ёмкостью, превышающей её длину. Рассмотрим следующую программу:
Представление строки в памяти
Как известно в C для представления строк используется правило PWSZ, что расшифровывается как Pointer to Wide-character String, Zero-terminated. При таком расположении в памяти в конце строки находится null-терминированный символ, по которому мы можем определить конец строки. Длина строки в PWSZ ограничена лишь объемом свободной оперативной памяти.
С BSTR дело обстоит немного иначе.
Основные особенности представления строки в памяти согласно правилу BSTR:
- Длина строки ограничена неким числом в отличие от PWSZ, где длина строки ограничена наличием свободной памяти;
- BSTR строка всегда указывает на первый символ в буфере. PWSZ может указывать на любой символ в буфере;
- У BSTR всегда в конце находится null символ, так же как и у PWSZ, но в отличие от последнего он является валидным символом и может встречаться в строке где угодно;
- За счет наличия null-символа в конце BSTR совместим с PWSZ, но не наоборот.
Использование такой реализации имеет ряд преимуществ:
- длину строки не нужно пересчитывать она хранится в заголовке объекта;
- строка может содержать null-символы, где угодно;
- и самое главное адрес строки (pinned пришпиленной) можно без проблем передавать в неуправляемой код там, где ожидается WCHAR*.
Сколько памяти занимает объект строкового типа?
Начнем с того, что строка является ссылочным типом данных, поэтому имеет так называемый заголовок объекта, который присущь всем ссылочным типам: первые 4 байта содержат SyncBlockIndex, а вторые 4 байта содержат указатель на тип (MethodTablePointer).
Итак,
Размер строки = 4 + 4 + .
Как было сказано выше, в буфере хранится длина строки — это поле типа int, значит еще 4 байта.
Размер строки = 4 + 4 + 4 + .
Для того, чтобы быстро передать строку в неуправляемый код (без копирования) в конце каждой строки стоит null-терминированный символ, который занимает 2 байта, значит
Размер строки = 4 + 4 + 4 + 2 + .
Вспомним так же, что каждый символ в строке находится в UTF -16 кодировке значит, занимает так же 2 байта, следовательно
Размер строки = 4 + 4 + 4 + 2 + 2 * length = 14 + 2 * length
Учтем еще один нюанс, и мы у цели. А именно менеджер памяти в CLR выделяет память кратной 4-ём байтам (4, 8, 12, 16), то есть если длина строки суммарно будет занимать 34 байта, то для нее будет выделено 36 байта. Нам необходимо округлить наше значение к ближайшему большему кратному четырем числу, для этого необходимо:
Размер строки = 4 * ((14 + 2 * length + 3) / 4) (деление естественно целочисленное)
Особенности строк
- Строки являются ссылочными типами;
- Строки неизменяемы. Однажды, создав строку, мы больше не можем ее изменить (честным способом). Каждый вызов метода этого класса возвращает новую строку, а предыдущая строка становится добычей для сборщика мусора;
- Строки переопределяют метод Object.Equals, в результате чего он сравнивает не значения ссылок, а значения символов в строках.
Рассмотрим каждый пункт подробнее.
Строки — ссылочные типы
Строки являются настоящими ссылочными типами, то есть они всегда располагаются в управляемой куче. Многие путают их со значимыми типами, потому что они ведут себя также, например, они неизменяемы и их сравнение происходит по значению, а не по ссылкам, но нужно помнить, что это ссылочный тип.
Строки — неизменяемы
Строки являются неизменяемыми. Это сделано не просто так. В неизменности строк есть немало преимуществ:
Строки переопределяют Object.Equals
Класс String переопределяет метод Object.Equals, в результате чего сравнение происходит не по ссылке, а по значению. Я думаю, разработчики благодарны создателям класса String за то, что они переопределили оператор ==, так как код, использующий == для сравнения строк, выглядит более изящно, нежели вызов метода.
Интернирование строк
Ну, и на последок поговорим об интернировании строк.
Рассмотрим простой пример: код который переворачивает строку.
Очевидно, данный код не скомпилируется. Компилятор будет ругаться на эти строки, потому что мы пытаемся изменить содержимое строки. Действительно, любой метод класса String возвращает новый экземпляр строки, вместо того чтобы изменять свое содержимое.
На самом деле строку можно изменить, но для этого придется прибегнуть к unsafe коду. Рассмотрим пример:
После выполнения этого кода, как и ожидалось, в строке будет записано elbatummi era sgnirtS .
Тот факт, что строки являются все-таки изменяемыми, приводит к одному очень интересному казусу. Связан он с интернированием строк.
Если не вникать глубоко в подробности, то смысл интернирования строк заключается в следующем: в рамках процесса (именно процесса, а не домена приложения) существует одна внутренняя хеш-таблица, ключами которой являются строки, а значениями – ссылки на них. Во время JIT-компиляции литеральные строки последовательно заносятся в таблицу (каждая строка в таблице встречается только один раз). На этапе выполнения ссылки на литеральные строки присваиваются из этой таблицы. Можно поместить строку во внутреннюю таблицу во время выполнения с помощью метода String.Intern. Также можно проверить, содержится ли строка во внутренней таблице с помощью метода String.IsInterned.
Важно отметить, что интернируются по умолчанию только строковые литералы. Поскольку для реализации интернирования используется внутренняя хеш-таблица, то во время JIT компиляции происходит поиск по ней, что занимает время, поэтому если бы интернировались все строки, то это свело бы на нет всю оптимизацию. Во время компиляции в IL код, компилятор конкатенирует все литеральные строки, так как нет в необходимости содержать их по частям, поэтому 2-ое равенство возвращает true. Так вот, в чем заключается казус. Рассмотрим следующий код:
Кажется, что здесь все очевидно и, что такой код должен распечатать Strings are immutable. Однако, нет! Код напечатает elbatummi era sgnirtS. Дело именно в интернировании, изменяя строку s, мы меняем ее содержимое, а так как она является литералом, то интернируется и представляется одним экземпляром строки.
От интернирования строк можно отказаться, если применить специальный атрибут CompilationRelaxationsAttribute к сборке.
Атрибут CompilationRelaxationsAttribute контролирует точность кода, создаваемого JIT-компилятором среды CLR. Конструктор данного атрибута принимает перечисление CompilationRelaxations в состав, которого на текущий момент входит только CompilationRelaxations.NoStringInterning — что помечает сборку как не требующую интернирования.
А что если без unsafe?
Таким образом, используя следующий код, можно изменить содержимое строки, даже не прибегая к использованию unsafe коду.
Этот код как уже ожидалось, напечатает elbatummi era sgnirtS .
Особенности производительности
У интернирования есть отрицательный побочный эффект. Дело в том, что ссылка на интернированный объект String, которую хранит CLR, может сохраняться и после завершения работы приложения и даже домена приложения. Поэтому большие литеральные строки использовать не стоит или же, если это необходимо стоит отключить интернирование, применив атрибут CompilationRelaxations к сборке.
Заключение
если я объявляю переменную типа std :: string, но не инициализирую ее, сколько памяти выделяется? Я знаю, что если я инициализирую его, например, как «привет», будет зарезервирован байт для каждого символа плюс один для нулевого символа, всего 6. Есть ли длина по умолчанию, определенная где-то в строковом классе?
(Я пытался найти собственно определение в файле заголовка строки, но не знаю, где его найти)
Решение
Это не указано. Различные реализации могут выделять разные объемы памяти при построении по умолчанию, и реализация не обязана сообщать вам, сколько это памяти. Тем не менее, я считаю, что сейчас это наиболее распространено для std::string использовать оптимизацию короткой строки, в соответствии с которой построена по умолчанию std::string не нужно выделять какую-либо память вообще, кроме размера std::string сам класс. Увидеть Значение аббревиатуры SSO в контексте std :: string для деталей. Обратите внимание, что sizeof(std::string) также не указано.
Другие решения
Хотя это не определено, стоит упомянуть кое-что: на практике реализации избегают выделения памяти для неинициализированных строк.
Я выполнил несколько тестов для обычного sizeof(std::string) с помощью Compiler Explorer (ссылка на тест ). Вот результаты, хотя вы, конечно, можете поэкспериментировать с другими:
- gcc / Linux / x86-64 / libstdc ++: 32 байта
- gcc / Linux / x86 / libstdc ++: 24 байта
- clang / Linux / x86-64 / libc ++ (примечание: не libstdc ++!): 24 байта
- clang / Linux / x86 / libc ++: 12 байт
- среда выполнения msvc / Windows / x86-64 / VC: 32 байта
- среда выполнения msvc / Windows / x86 / VC: 24 байта
- gcc / Linux / ARM64 / libstdc ++: 32 байта
- gcc / Linux / ARM (32-разрядная версия) / libstdc ++: 24 байта
- среда выполнения msvc / Windows / ARM64 / VC: 32 байта (Примечание: вывод в шестнадцатеричном формате на CE)
Грубо говоря, на практике std::string объект будет иметь размер от 12 до 32 байт, за исключением динамически выделяемой памяти.
Эти результаты зависят в основном от реализации стандартной библиотеки и архитектуры процессора (из-за того, что стандартная библиотека чувствует, что делает).
Обратите внимание, что эти размеры делать включить SSO, о котором @Brian рассказывал в своем ответе. Я полагаю, что это мотив для реализации libstdc ++ и MS использовать 32 байта, а не 24 (поскольку я подозреваю, что обычно задействованы 3 указателя: начало данных, конец данных и конец емкости), хотя я не знаю специфики.
Вы всегда можете проверить себя. Это зависит от компьютера, на котором вы находитесь, чтобы проверить размеры типов данных, которые вы всегда можете использовать size:of ,
Читайте также: