Накладные расходы на использование «нового» для создания объекта в динамической памяти по сравнению с автоматическим созданием в стеке
Предположим, у меня есть класс с именем Foo
, а размер sizeof(Foo)
составляет 10 байт.
Насколько я понимаю, создание Foo с «автоматической» областью действия (в стеке) не имеет накладных расходов. Если объект занимает 10 байт, он занимает 10 байт в стеке. Точка.
Я немного не понимаю, как создание объектов с помощью «нового» работает на 8-битном AVR. Я знаю, что «новый» в конечном итоге вызывает malloc(), а в Windows/Linux malloc() в конечном итоге запрашивает у операционной системы память, управление которой осуществляется самой ОС. Но... с 8-битным AVR нет операционной системы. Итак, по-видимому, конечную ответственность за управление этой кучей несет кто-то другой. И, по-видимому, здесь задействованы какие-то накладные расходы, чтобы он мог отслеживать, какие байты были возвращены ему деструктором, а какие все еще используются.
Итак... используя гипотетический 10-байтовый Foo
в качестве примера... если я сделаю 7 последовательных вызовов подряд к new Foo()
, как сколько байтов на самом деле будет сожжено (включая служебные данные)?
Для объектов, экземпляры которых необходимо создать с помощью new
, но которые будут сохраняться «навсегда»; (до следующего сброса), существует ли какой-либо специальный синтаксис, который сообщает компилятору/malloc()/чему бы то ни было: «Этот объект никогда не будет уничтожен, поэтому нет необходимости сохранять какую-либо информацию, которая в противном случае была бы уничтожена». нужно ли в конечном итоге освободить его память"?
Для контекста предположим, что View
— это класс с несколькими шаблонными подклассами (скажем, TemperatureView<int, int, int, int>
и >LabelView<int, int, int, const char*>
), и я делаю что-то вроде этого:
const PROGMEM char* pLABEL = "Foo:";
View* views[] = {
(new TemperatureView<0,3,0,2>{}),
(new LabelView<1,4,0,pLABEL>{})
};
Я пытаюсь выяснить, насколько велик штрафной удар (с точки зрения потраченного впустую ОЗУ), который я принимаю, сводя все это в одно красивое, аккуратное комбинированное объявление вместо того, чтобы делать что-то вроде:
>const PROGMEM char* pLABEL = "Foo:";
TemperatureView<0,3,0,2> firstTemp {};
LabelView<1,4,0,pLABEL> firstLabel {};
View* views[] = {
&firstTemp,
&firstLabel
};
(Изначально я хотел поместить объекты, адреса которых добавлялись в представления[] в автоматическом/стеке, но, видимо, это табу, поскольку компилятор считает, что массив переживет объекты, адреса которых в противном случае хранились бы там)
@Bitbang3r, 👍1
2 ответа
Лучший ответ:
Если вы создаете автоматическую переменную, ее время жизни равно времени жизни функции, внутри которой она находится, поэтому она не будет сохраняться вечно, если только она не находится в глобальной области видимости или не объявлена static
.
Накладные расходы на использование new
— это размер выделяемого объекта плюс пара байтов для отслеживания того, для чего используется этот бит памяти.
если я сделаю 7 последовательных вызовов метода new Foo(), сколько байт он фактически сожжет (включая накладные расходы, которые составляют два байта, см. тест ниже)
В семь раз больше размера объекта плюс упомянутые мной накладные расходы.
Обычно вы освобождаете эту память с помощью delete
, если только вы не хотите, чтобы она сохранялась навсегда.
Для объектов, экземпляры которых должны быть созданы с помощью new, но будут сохраняться «навсегда»; (до следующего сброса), существует ли какой-либо специальный синтаксис, который сообщает компилятору/malloc()/что угодно: «Этот объект никогда не будет уничтожен...
Нет. Оно сохраняется до тех пор, пока вы не вызовете delete
, что не вызывает угрызений совести.
Я написал тестовую программу для своего Uno, чтобы увидеть, каковы накладные расходы, чтобы использовать new
.
#include "memdebug.h"
void showFreeMemory ()
{
Serial.print (F("Free memory = "));
Serial.println (getFreeMemory ());
} // конец showFreeMemory
typedef struct {char s [20]; } foo;
void setup ()
{
Serial.begin (115200);
Serial.println ();
showFreeMemory (); // 1664
foo * bar = new foo;
showFreeMemory (); // 1642
strcpy (bar->s, "Hello, world");
Serial.println (bar->s);
delete bar;
showFreeMemory (); // 166 4
} // конец настройки
void loop () { }
Я пытаюсь выяснить, насколько велик штрафной удар (с точки зрения потраченного впустую толчка сверху)
Судя по всему, два байта для каждого распределения.
(Изначально я хотел поместить объекты, адреса которых добавлялись в представления[] в автоматическом/стеке, но, видимо, это табу, поскольку компилятор считает, что массив переживет объекты, адреса которых в противном случае хранились бы там)
Я не понимаю, что вы имеете в виду, можете ли вы привести конкретный пример?
Я думаю, вы сэкономите больше памяти, используя PROGMEM, что вы, похоже, немного и делаете.
См. мой пост здесь, чтобы узнать, как это сделать.
Ссылка
Я получил memdebug.h (и memdebug.c) из Семинар Энди - Отладка динамического распределения памяти AVR
Как небольшое дополнение к предыдущему ответу... Документация
в avr-libc есть целая страница, обсуждающая работу malloc()
.
В разделе Детали реализации говорится:
Запросы на динамическое выделение памяти будут возвращены с двухбайтовым кодом. добавленный заголовок, в котором записан размер выделения. Это позже используется
free()
. Возвращенный адрес указывает прямо за этим заголовок.
Это соответствует экспериментальному выводу Ника Гаммона.
- C++ против языка Arduino?
- Как использовать SPI на Arduino?
- Какие накладные расходы и другие соображения существуют при использовании структуры по сравнению с классом?
- Ошибка: expected unqualified-id before 'if'
- Что лучше использовать: #define или const int для констант?
- Функции со строковыми параметрами
- Библиотека DHT.h не импортируется
- ошибка: ожидаемое первичное выражение перед токеном ','
Очень хороший. Мне было интересно, почему накладные расходы составляют всего два байта, поскольку я думал, что вам могут понадобиться длина и указатель, но только сохранение длины означает, что вы можете вывести адрес следующего фрагмента. Затем, когда вы освобождаете фрагменты, по-видимому, существует «свободный список», и они используют (теперь заброшенную) часть памяти для записи того, где находится следующий свободный фрагмент. Предположительно, поэтому минимальный объем, который выделяется, составляет два байта (плюс длина)., @Nick Gammon