Накладные расходы на использование «нового» для создания объекта в динамической памяти по сравнению с автоматическим созданием в стеке

c++

Предположим, у меня есть класс с именем 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
};

(Изначально я хотел поместить объекты, адреса которых добавлялись в представления[] в автоматическом/стеке, но, видимо, это табу, поскольку компилятор считает, что массив переживет объекты, адреса которых в противном случае хранились бы там)

, 👍1


2 ответа


Лучший ответ:

1

Если вы создаете автоматическую переменную, ее время жизни равно времени жизни функции, внутри которой она находится, поэтому она не будет сохраняться вечно, если только она не находится в глобальной области видимости или не объявлена 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

,

1

Как небольшое дополнение к предыдущему ответу... Документация в avr-libc есть целая страница, обсуждающая работу malloc(). В разделе Детали реализации говорится:

Запросы на динамическое выделение памяти будут возвращены с двухбайтовым кодом. добавленный заголовок, в котором записан размер выделения. Это позже используется free(). Возвращенный адрес указывает прямо за этим заголовок.

Это соответствует экспериментальному выводу Ника Гаммона.

,

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