4.3 – Размеры объектов и оператор sizeof
Как вы узнали из урока «4.1 – Введение в основные типы данных», память на современных машинах обычно организована в блоки размером с байты, причем каждый байт памяти имеет уникальный адрес. До этого момента было полезно думать о памяти как о связке почтовых ящиков, куда мы можем помещать и извлекать информацию, а переменные в этой аналогии – имена для доступа к этим почтовым ящикам.
Однако эта аналогия не совсем верна в одном отношении – большинство объектов на самом деле занимают более 1 байта памяти. Один объект может использовать 2, 4, 8 или более последовательных адресов памяти. Объем памяти, который использует объект, зависит от его типа данных.
Поскольку мы обычно обращаемся к памяти через имена переменных (а не напрямую через адреса памяти), компилятор может скрыть от нас детали того, сколько байтов использует какой-либо конкретный объект. Когда мы обращаемся к некоторой переменной x , компилятор знает, сколько байтов данных нужно получить (в зависимости от типа переменной x ), и может справиться с этой задачей за нас.
Тем не менее, есть несколько причин, по которым полезно знать, сколько памяти использует какой-либо объект.
Во-первых, чем больше памяти использует объект, тем больше информации он может вместить.
Один бит может содержать 2 возможных значения, 0 или 1:
| бит 0 |
|---|
| 0 |
| 1 |
2 бита могут содержать 4 возможных значения:
| бит 0 | бит 1 |
|---|---|
| 0 | 0 |
| 0 | 1 |
| 1 | 0 |
| 1 | 1 |
3 бита могут содержать 8 возможных значений:
| бит 0 | бит 1 | бит 2 |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 0 | 1 |
| 0 | 1 | 0 |
| 0 | 1 | 1 |
| 1 | 0 | 0 |
| 1 | 0 | 1 |
| 1 | 1 | 0 |
| 1 | 1 | 1 |
В общем, объект из n битов (где n – целое число) может содержать 2 n (2 в степени n, также иногда записывается 2^n) уникальных значений. Следовательно, при байте из 8 битов, объект размером 1 байт может принимать 2 8 (256) различных значений. Объект, который использует 2 байта, может принимать 2^16 (65536) разных значений!
Таким образом, размер объекта ограничивает количество уникальных значений, которые он может принимать – объекты, которые используют больше байтов, могут принимать большее количество уникальных значений. Мы рассмотрим это дальше, когда поговорим подробнее о целых числах.
Во-вторых, у компьютеров объем свободной памяти ограничен. Каждый раз, когда мы определяем объект, небольшая часть этой свободной памяти используется, пока существует объект. Поскольку у современных компьютеров много памяти, это влияние обычно незначительно. Однако для программ, которым требуется большое количество объектов или данных (например, игра, которая отображает миллионы полигонов), разница между использованием 1- и 8-байтовых объектов может быть значительной.
Ключевой момент
Начинающие программисты часто слишком много внимания уделяют оптимизации своего кода, чтобы использовать как можно меньше памяти. В большинстве случаев достигаемая разница незначительна. Сосредоточьтесь на написании поддерживаемого кода и оптимизируйте его только тогда и там, где выгода будет существенной.
Размеры основных типов данных
Следующий очевидный вопрос – «сколько памяти занимают переменные разных типов данных?». Вы можете быть удивлены, обнаружив, что размер конкретного типа данных зависит от компилятора и/или архитектуры компьютера!
C++ гарантирует только минимальный размер каждого базового типа данных:
| Категория | Тип | Минимальный размер | Примечание |
|---|---|---|---|
| логический | bool | 1 байт | |
| символ | char | 1 байт | всегда точно 1 байт |
| wchar_t | 1 байт | ||
| char16_t | 2 байта | тип C++11 | |
| char32_t | 4 байта | тип C++11 | |
| целочисленное значение | short | 2 байта | |
| int | 2 байта | ||
| long | 4 байта | ||
| long long | 8 байт | тип C99/C++11 | |
| с плавающей запятой | float | 4 байта | |
| double | 8 байт | ||
| long double | 8 байт |
Однако фактический размер переменных на вашем компьютере может быть другим (особенно int , который чаще всего составляет 4 байта).
Лучшая практика
Для максимальной совместимости не следует предполагать, что переменные могут быть больше указанного минимального размера.
Объекты базовых типов данных обычно работают очень быстро.
Оператор sizeof
Чтобы определить размеры типов данных на конкретной машине, C++ предоставляет оператор с именем sizeof . Оператор sizeof – это унарный оператор, который принимает тип или переменную и возвращает ее размер в байтах. Вы можете скомпилировать и запустить следующую программу, чтобы узнать размеры некоторых из ваших типов данных:
Вот результат работы этой программы, полученный автором, на машине x64 при использовании Visual Studio:
Ваши результаты могут отличаться, если вы используете другой тип машины или другой компилятор. Обратите внимание, что вы не можете использовать оператор sizeof для типа void , поскольку он не имеет размера (это приведет к ошибке компиляции).
Для продвинутых читателей
Если вам интересно, что такое " \t " в приведенной выше программе, это специальный символ, который вставляет табуляцию (в этом примере мы используем ее для выравнивания выходных столбцов). Мы рассмотрим " \t " и другие специальные символы в уроке «4.11 – Символы».
Вы также можете использовать оператор sizeof для имени переменной:
Производительность при использовании базовых типов данных
На современных машинах объекты базовых типов данных работают быстро, поэтому производительность при использовании этих типов обычно не должна быть проблемой.
В качестве отступления.
Вы можете предположить, что типы, которые используют меньше памяти, будут быстрее, чем типы, которые используют больше памяти. Это не всегда так. Процессоры часто оптимизированы для обработки данных определенного размера (например, 32 бита), и типы, соответствующие этому размеру, могут обрабатываться быстрее. На такой машине 32-битный int может быть быстрее, чем 16-битный short или 8-битный char .
sizeof operator
Depending on the computer architecture, a byte may consist of 8 or more bits, the exact number provided as CHAR_BIT .
sizeof ( char ) , sizeof ( signed char ) , and sizeof ( unsigned char ) always return 1 .
sizeof cannot be used with function types, incomplete types (including void ), or bit field lvalues.
When applied to an operand that has structure or union type, the result is the total number of bytes in such an object, including internal and trailing padding. The trailing padding is such that if the object were an element of an array, the alignment requirement of the next element of this array would be satisfied, in other words, sizeof ( T ) returns the size of an element of a T[] array.
If type is a VLA type and changing the value of its size expression would not affect the result of sizeof , it is unspecified whether or not the size expression is evaluated.
Except if the type of expression is a VLA, (since C99) expression is not evaluated and the sizeof operator may be used in an integer constant expression.
If the type of expression is a variable-length array type, expression is evaluated and the size of the array it evaluates to is calculated at run time.
Number of elements in any array a including VLA (since C99) may be determined with the expression sizeof a / sizeof a [ 0 ] . Note that if a has pointer type (such as after array-to-pointer conversion of function parameter type adjustment), this expression would simply divide the number of bytes in a pointer type by the number of bytes in the pointed type.
[edit] Keywords
[edit] Example
sample output corresponds to a platform with 64-bit pointers and 32-bit int
sizeof operator
Используется,когда реальный размер объекта должен быть известен.
Syntax
| sizeof( type ) | (1) |
| sizeof expression | (2) |
Обе версии возвращают значение типа size_t .
Explanation
Notes
В зависимости от архитектуры компьютера байт может состоять из 8 или более битов, точное число CHAR_BIT как CHAR_BIT .
sizeof(char) , sizeof(signed char) и sizeof(unsigned char) всегда возвращают 1 .
sizeof не может использоваться с типами функций, неполными типами (включая void ) или значениями битовых полей .
При применении к операнду, имеющему тип структуры или объединения , результатом является общее количество байтов в таком объекте, включая внутреннее и завершающее заполнение. Завершающее заполнение таково, что если бы объект был элементом массива, требование выравнивания следующего элемента этого массива было бы выполнено, другими словами, sizeof(T) возвращает размер элемента массива T[] .
Если тип является типом VLA и изменение значения его выражения размера не повлияет на результат sizeof , не указано, вычисляется ли выражение размера.
За исключением случаев, когда типом выражения является VLA , (с C99) выражение не оценивается, и оператор sizeof может использоваться в выражении с целочисленной константой .
Если тип выражения является типом массива переменной длины , выражение вычисляется, а размер массива, который он оценивает, вычисляется во время выполнения.
Количество элементов в любом массиве включая VLA (так как C99) может быть определена с выражением sizeof a / sizeof a[0] . Обратите внимание, что если a имеет тип указателя (например, после преобразования массива в указатель при настройке типа параметра функции), это выражение будет просто делить число байтов в типе указателя на количество байтов в указанном типе. a
Keywords
Example
Пример вывода соответствует платформе с 64-битными указателями и 32-битными int.
What does sizeof do?
What do sizeof (k) or sizeof (int) and sizeof (char) mean?
4 Answers 4
sizeof(x) returns the amount of memory (in bytes) that the variable or type x occupies. It has nothing to do with the value of the variable.
For example, if you have an array of some arbitrary type T then the distance between elements of that array is exactly sizeof(T) .
When used on a variable, it is equivalent to using it on the type of that variable:
As a rule-of-thumb, it is best to use the variable name where possible, just in case the type changes:
When used on user-defined types, sizeof still returns the amount of memory used by instances of that type, but it’s worth pointing out that this does not necessary equal the sum of its members.
While sizeof(int) + sizeof(char) is typically 5 , on many machines, sizeof(Foo) may be 8 because the compiler needs to pad out the structure so that it lies on 4 byte boundaries. This is not always the case, and it’s quite possible that on your machine sizeof(Foo) will be 5, but you can’t depend on it.