Вопрос даже не в том почему во втором тесте один экземляр занимает 32 байта. А в том почему в третьем тесте экземпляр не занимает 40 байт.
16 золотых знаков56 серебряных знаков97 бронзовых знаков
задан 2 фев 2018 в 7:45
Во первых, в данном коде неправильно измеряется размер для массива ссылочных типов. Код:
Измеряет память под массив ссылок + память под объекты. Надо так:
Во вторых, арифметика Размер C = Размер object + 2 * Размер int не работает: все несколько сложнее.
Для 64-разрядной версии минимальный размер 2 * 8 + 8 = 24. Размер типа, меньшего 24 байта, дополняется до 24.
(Определение ObjHeader здесь: https://github.com/dotnet/coreclr/blob/master/src/gc/env/gcenv.object.h)
Размер объекта с 1 int полем = 24 байта
Размер объекта с 2 int полями = 24 байта
Размер объекта с 3 int полями = 32 байта
ответ дан 2 фев 2018 в 10:58
6 золотых знаков37 серебряных знаков81 бронзовый знак
Сколько памяти занимают ссылки?
задан 20 ноя 2011 в 15:59
Если ссылка является просто алиасом переменой, то скорее всего ничего не занимает (будет оптимизирована компилятором), а если ссылка — аргумент функции, то занимает столько же, сколько и указатель.
sizeof в данном случае использовать нельзя, так как в c++ ссылки «прозрачны». Оператор sizeof в случае применения к ссылке «думает», что нужно вывести размер объекта на который она ссылается.
8 золотых знаков69 бронзовых знаков
ответ дан 20 ноя 2011 в 20:52
14 бронзовых знаков
Ссылки в языке С++ являются реализацией концепции альтернативного имени для существующего объекта. Т.е. на уровне концепций языка, ссылки не занимают никакой памяти вообще. Ссылки в языке С++ не являются объектами и по этой причине формально не имеют места в хранилище (storage).
Другими словами, на уровне языка вот в таком вот примере
int x = 5;
int &r = x;
нет никакой разницы между “основным” именем объявленной переменной x и ее альтернативным именем r. Основное имя переменной никакой памяти для себя не требует, соответственно и альтернативное имя тоже никакой памяти не требует.
Все вышесказанное справедливо на уровне языка, но, как правило, практическая реализация языковых концепций обычно требует дополнительных накладных расходов. Понятно, что полная аналогия между ссылкой и именем объекта возможна только в том случае, когда значение ссылки предсказуемо на стадии компиляции. В такой ситуации каждый уважающий себя компилятор просто “отоптимизирует” такую ссылку нафиг и никакой памяти она занимать не будет.
Во всех остальных случаях ссылки реализуются компилятором, как “замаскированные” указатели, т.е. непереназначаемые указатели, к которым не надо применять оператор * для разыменования. Соответственно и памяти такая ссылка занимают ровно столько, сколько указатель.
Вот и все.
ответ дан 16 июл 2015 в 18:27
Попробуем такой тестовый код:
Ubuntu 11.04 x86_64, g++-4.5
Вывод – ссылка имеет тот же размер, что и объект (в широком смысле), на который она ссылается.
На самом деле, ссылка в памяти не занимает нисколько места, потому что это просто иное имя для переменной. Другое дело при передачи параметра по ссылке в функцию. Там скорее всего реально передается указатель, но нужно посмотреть генерируемый ассемблеровский код.
P.S. Это очень распространенная ошибка – путать ссылку с указателем.
ответ дан 20 ноя 2011 в 20:42
2 золотых знака37 серебряных знаков69 бронзовых знаков
В зависимости от разрядности платформы. Если компилировать под x86, то 4 байта, если x64 – то 8.
ответ дан 20 ноя 2011 в 16:16
20 серебряных знаков32 бронзовых знака
Ссылка как правило реализуется как обертка над указателем, насколько я помню в стандарте нет требований к реализации ссылок. Там описано только как ссылка должна себя вести.
Но если уж вам необходимо знать размер самой ссылки, то sizeof(T &) вам не поможет, вы получете размер T. Однако можно сделать нехитрый финт ушами – завернуть ссылку в структуру:
Так вот, у меня под VC2012 предсказуемо f = 128 и для x86 и для x64. Под x86 b = 4, под x64 b = 8, что тоже предсказуемо.
ответ дан 10 июл 2015 в 8:11
2 золотых знака43 бронзовых знака
Провел эксперимент (у меня система х32)
в итоге размеры
&a = pa = &b = pb = &c = pc = &d = pd = 4 байта
a = *pa = 4 байта
b = *pb = 2 байта
c = *pc = 10 байт
d = *pd = 1 байт
Вывод адресс занимает 4 байта
Насчет того занимает ли место ссылка в программе не знаю как проверить
ответ дан 10 июл 2015 в 7:06
Если ты хотел бы узнать сколько занимает указатель на некоторый тип, то это,
как сказал GLmonster, зависит от разрядности платформы.
То есть если мы создаём
int a=5;
int *b=&a;
то b указывает на a и занимает 4 байта.
При выделении памяти
int a=5;
int &b=a;
происходит почти то же самое. Мы в неявном виде выделяем указатель b (4 байта)
и говорим что он указывает на a.
Вот этой командой мы задаём статически память для a.
То есть после компиляции в exe будет находится информация о том как будут распологаться ячейки памяти в RAM и как к ним обратиться. После запуска приложения произойдёт определение их в оперативной памяти. Для типа указателей та же картина. Но на что он будет указывать мы можем изменить в ходе программы.
Например так:
int a=5;
int &b=a;
int w=5;
&b=&w;
В данном случае так как b указатель мы попросим чтобы он указывал на память w и нам разрешат это сделать.
Надеюсь в тему и всё понятно. =)
ответ дан 20 ноя 2011 в 19:21
Ссылка – это просто скрытый указатель.В 32-битных программах это 4 байта, а в 64-битных – 8.
Размер указателя можно вывести при помощи sizeof:
ответ дан 16 июл 2015 в 15:58
24 золотых знака121 серебряный знак295 бронзовых знаков
Добавлено 26 апреля 2021 в 21:11
Размеры объектов
Как вы узнали из урока «4.1 – Введение в основные типы данных», память на современных машинах обычно организована в блоки размером с байты, причем каждый байт памяти имеет уникальный адрес. До этого момента было полезно думать о памяти как о связке почтовых ящиков, куда мы можем помещать и извлекать информацию, а переменные в этой аналогии – имена для доступа к этим почтовым ящикам.
Однако эта аналогия не совсем верна в одном отношении – большинство объектов на самом деле занимают более 1 байта памяти. Один объект может использовать 2, 4, 8 или более последовательных адресов памяти. Объем памяти, который использует объект, зависит от его типа данных.
Поскольку мы обычно обращаемся к памяти через имена переменных (а не напрямую через адреса памяти), компилятор может скрыть от нас детали того, сколько байтов использует какой-либо конкретный объект. Когда мы обращаемся к некоторой переменной x, компилятор знает, сколько байтов данных нужно получить (в зависимости от типа переменной x), и может справиться с этой задачей за нас.
Во-первых, чем больше памяти использует объект, тем больше информации он может вместить.
Один бит может содержать 2 возможных значения, 0 или 1:
2 бита могут содержать 4 возможных значения:
3 бита могут содержать 8 возможных значений:
В общем, объект из n битов (где n – целое число) может содержать 2n (2 в степени n, также иногда записывается 2^n) уникальных значений. Следовательно, при байте из 8 битов, объект размером 1 байт может принимать 28 (256) различных значений. Объект, который использует 2 байта, может принимать 2^16 (65536) разных значений!
Таким образом, размер объекта ограничивает количество уникальных значений, которые он может принимать – объекты, которые используют больше байтов, могут принимать большее количество уникальных значений. Мы рассмотрим это дальше, когда поговорим подробнее о целых числах.
Во-вторых, у компьютеров объем свободной памяти ограничен. Каждый раз, когда мы определяем объект, небольшая часть этой свободной памяти используется, пока существует объект. Поскольку у современных компьютеров много памяти, это влияние обычно незначительно. Однако для программ, которым требуется большое количество объектов или данных (например, игра, которая отображает миллионы полигонов), разница между использованием 1- и 8-байтовых объектов может быть значительной.
Начинающие программисты часто слишком много внимания уделяют оптимизации своего кода, чтобы использовать как можно меньше памяти. В большинстве случаев достигаемая разница незначительна. Сосредоточьтесь на написании поддерживаемого кода и оптимизируйте его только тогда и там, где выгода будет существенной.
Размеры основных типов данных
Следующий очевидный вопрос – «сколько памяти занимают переменные разных типов данных?». Вы можете быть удивлены, обнаружив, что размер конкретного типа данных зависит от компилятора и/или архитектуры компьютера!
C++ гарантирует только минимальный размер каждого базового типа данных:
Однако фактический размер переменных на вашем компьютере может быть другим (особенно int, который чаще всего составляет 4 байта).
Для максимальной совместимости не следует предполагать, что переменные могут быть больше указанного минимального размера.
Объекты базовых типов данных обычно работают очень быстро.
Оператор sizeof
Чтобы определить размеры типов данных на конкретной машине, C++ предоставляет оператор с именем sizeof. Оператор sizeof – это унарный оператор, который принимает тип или переменную и возвращает ее размер в байтах. Вы можете скомпилировать и запустить следующую программу, чтобы узнать размеры некоторых из ваших типов данных:
Вот результат работы этой программы, полученный автором, на машине x64 при использовании Visual Studio:
bool: 1 bytes
char: 1 bytes
wchar_t: 2 bytes
char16_t: 2 bytes
char32_t: 4 bytes
short: 2 bytes
int: 4 bytes
long: 4 bytes
long long: 8 bytes
float: 4 bytes
double: 8 bytes
long double: 8 bytes
Ваши результаты могут отличаться, если вы используете другой тип машины или другой компилятор. Обратите внимание, что вы не можете использовать оператор sizeof для типа void, поскольку он не имеет размера (это приведет к ошибке компиляции).
Для продвинутых читателей
Если вам интересно, что такое ” ” в приведенной выше программе, это специальный символ, который вставляет табуляцию (в этом примере мы используем ее для выравнивания выходных столбцов). Мы рассмотрим ” ” и другие специальные символы в уроке «4.11 – Символы».
Вы также можете использовать оператор sizeof для имени переменной:
x is 4 bytes
Производительность при использовании базовых типов данных
На современных машинах объекты базовых типов данных работают быстро, поэтому производительность при использовании этих типов обычно не должна быть проблемой.
Вы можете предположить, что типы, которые используют меньше памяти, будут быстрее, чем типы, которые используют больше памяти. Это не всегда так. Процессоры часто оптимизированы для обработки данных определенного размера (например, 32 бита), и типы, соответствующие этому размеру, могут обрабатываться быстрее. На такой машине 32-битный int может быть быстрее, чем 16-битный short или 8-битный char.
Теги
Добавлено 2 октября 2021 в 16:01
После того, как вы создали строку, часто бывает полезно узнать ее длину. Здесь в игру вступают операции с длиной и емкостью.
Длина строки
Длина строки – это довольно просто, это количество символов в строке. Для определения длины строки есть две идентичные функции:
Length() const
size_type string
Обе эти функции возвращают текущее количество символов в строке, исключая завершающий ноль.
string sSource(“012345678”);
cout << sSource.length() << endl;
Хотя, чтобы определить, есть ли в строке какие-либо символы или нет, можно использовать length(), но более эффективно использовать функцию empty():
Empty() const
Возвращает true, если в строке нет символов, иначе – false.
string sString1(“Not Empty”);
cout << (sString1.empty() ? “true” : “false”) << endl;
string sString2; // пустая
cout << (sString2.empty() ? “true” : “false”) << endl;
Есть еще одна функция, связанная с размером, которую вы, вероятно, никогда не будете использовать, но мы опишем ее здесь для полноты картины:
Возвращает максимальное количество символов, которое может содержать строка. Это значение будет варьироваться в зависимости от операционной системы и архитектуры системы.
string sString(“MyString”);
cout << sString.max_size() << endl;
Емкость строки
Емкость (вместимость) строки показывает, сколько памяти выделено объектом строки для хранения ее содержимого. Это значение измеряется в строковых символах, исключая символ завершающего нуля. Например, строка с емкостью 8 может содержать 8 символов.
Возвращает количество символов, которое строка может хранить без перераспределения памяти.
string sString(“01234567”);
cout << “Length: ” << sString.length() << endl;
cout << “Capacity: ” << sString.capacity() << endl;
Length: 8
Capacity: 15
Обратите внимание, что емкость больше, чем длина строки! Хотя длина нашей строки равна 8, на самом деле она занимала достаточно памяти для 15 символов! Зачем так сделано?
Здесь важно понимать, что если пользователь хочет поместить в строку больше символов, чем позволяет ее емкость, то для получения большей емкости строка должна быть перераспределена в памяти. Например, если строка имеет длину и емкость 8, то добавление любых символов в строку приведет к переразмещению объекта в памяти. Сделав емкость больше размера фактической строки, пользователь получил некоторое буферное пространство для расширения строки до необходимости перераспределения.
Как оказалось, перераспределение – это плохо по нескольким причинам:
Во-первых, перераспределение строки относительно дорого. Сначала необходимо выделить новую память. Затем каждый символ в строке необходимо скопировать в новую память. Если строка большая, это может занять много времени. Наконец, необходимо освободить старую память. Если вы выполняете много перераспределений, этот процесс может значительно замедлить работу вашей программы.
Во-вторых, всякий раз, когда строка перераспределяется, адрес содержимого строки в памяти изменяется на новое значение. Это означает, что все ссылки, указатели и итераторы строки становятся недействительными!
Обратите внимание, что строки не всегда размещаются с емкостью, превышающей длину. Рассмотрим следующую программу:
string sString(“0123456789abcde”);
cout << “Length: ” << sString.length() << endl;
cout << “Capacity: ” << sString.capacity() << endl;
Эта программа выводит:
Length: 15
Capacity: 15
Результаты могут отличаться в зависимости от компилятора.
Давайте добавим к строке один символ и посмотрим, как изменится емкость:
string sString(“0123456789abcde”);
cout << “Length: ” << sString.length() << endl;
cout << “Capacity: ” << sString.capacity() << endl;
// Теперь добавим новый символ
sString += “f”;
cout << “Length: ” << sString.length() << endl;
cout << “Capacity: ” << sString.capacity() << endl;
Это дает следующий результат:
Length: 15
Capacity: 15
Length: 16
Capacity: 31
Reserve(size_type unSize)
Второй вариант этой функции устанавливает емкость строки минимум на unSize (она может быть больше). Обратите внимание, что для этого может потребоваться перераспределение.
Если вызывается первый вариант этой функции или второй вариант вызывается с unSize, меньшим, чем текущая емкость, функция попытается уменьшить емкость, чтобы она соответствовала длине. Этот запрос необязателен для выполнения.
string sString(“01234567”);
cout << “Length: ” << sString.length() << endl;
cout << “Capacity: ” << sString.capacity() << endl;
sString.reserve(200);
cout << “Length: ” << sString.length() << endl;
cout << “Capacity: ” << sString.capacity() << endl;
sString.reserve();
cout << “Length: ” << sString.length() << endl;
cout << “Capacity: ” << sString.capacity() << endl;
Length: 8
Capacity: 15
Length: 8
Capacity: 207
Length: 8
Capacity: 207
В этом примере показаны две интересные вещи. Во-первых, хотя мы запросили емкость 200, на самом деле мы получили емкость 207. Гарантируется, что емкость всегда будет не меньше, чем вы запросили, но может быть и больше. Затем мы запросили изменение емкости, чтобы она соответствовала длине строки. Этот запрос был проигнорирован, так как емкость не изменилась.
Если вы заранее знаете, что собираетесь создать большую строку, выполняя множество строковых операций, которые увеличивают размер строки, вы можете избежать многократного перераспределения строки в памяти, сразу установив для строки необходимую ей емкость:
Результат этой программы будет меняться каждый раз. Вот результат одного выполнения:
Вместо того чтобы перераспределять sString несколько раз, мы устанавливаем емкость один раз, а затем заполняем строку. Это может очень влиять на производительность при формировании больших строк с помощью конкатенации.
Что-то мне подсказывает, что нет, но хочу убедиться.
1. Способ не портируемый (компиляторо-зависимый)
2. Не точный (размер может получиться немножко больше, чем на самом деле)
В итоге: поддержка такой системы, это настоящее шаманство с пляской и бубнами. А сама операция в целом или дорогостоящая по времени, или реализована по довольно таки сложному алгоритму, который тоже будит жрать память.
В любом случае, поддержка такой системы дело будит трудоёмкое. Считай каждый класс твоей архитектуры должен будит иметь метод GetMemory()
/зы можно просто опрашивать систему на предмет “оставшейся свободной памяти”, ну или “сколько памяти скушал процесс”, В общем, такие вопросы лучше решать во взаимодействии с системой, а не гнать жосскую отсебятину.