Epydoc также является хорошим инструментом для документирования исходного кода и обрабатывает перекрестные ссылки на классы, модули, переменные и т. Д. В HTML, PDF, LaTeX. Рисует также хорошие диаграммы наследования классов. Он используется рядом проектов с открытым исходным кодом, поэтому он довольно активно развивается.
Примечание: Этот ответ слишком длинный . Я когда-нибудь его урежу. Между тем, прокомментируйте, если вы можете придумать полезные изменения.
Чтобы ответить на ваши вопросы, нам сначала нужно определить две области памяти, называемые стеком и кучей .
Представьте себе стопку как стопку коробок. Каждый прямоугольник представляет выполнение функции. В начале, когда вызывается main
, на полу стоит один ящик. Все определяемые вами локальные переменные находятся в этом поле.
int main(int argc, char * argv[])
{
int a = 3;
int b = 4;
return a + b;
}
В этом случае у вас есть один ящик на полу с переменными argc
(целое число), argv
(указатель на массив символов), a
(целое число), и b
(целое число).
int main(int argc, char * argv[])
{
int a = 3;
int b = 4;
return do_stuff(a, b);
}
int do_stuff(int a, int b)
{
int c = a + b;
c++;
return c;
}
Теперь у вас есть коробка на полу (для main
) с argc
, argv
, a
и b
. Поверх этого поля у вас есть еще одно поле (для do_stuff
) с a
, b
и c
.
Это Пример иллюстрирует два интересных эффекта.
Как вы, вероятно, знаете, a
и b
передавались по значению. Вот почему есть копия этих переменных в поле для do_stuff
.
Обратите внимание, что вы этого не делаете. Для этих переменных необходимо бесплатно
или удалить
или что-нибудь еще. Когда ваша функция возвращается, блок для этой функции уничтожается.
int main(int argc, char * argv[])
{
int a = 3;
int b = 4;
return do_stuff(a, b);
}
int do_stuff(int a, int b)
{
return do_stuff(a, b);
}
Здесь у вас есть ящик на полу (для main
, как и раньше). Затем у вас есть коробка (для do_stuff
) с a
и b
. Затем у вас есть еще один ящик (для do_stuff
, вызывающий сам себя), снова с a
и b
. А потом еще один. И вскоре у вас есть переполнение стека .
Думайте о стеке как о стопке ящиков. Каждое поле представляет собой выполняющуюся функцию, и это поле содержит локальные переменные, определенные в этой функции. Когда функция возвращается, этот блок уничтожается.
Здесь вступает в игру динамическое распределение памяти.
Представьте себе кучу как бесконечный зеленый луг памяти. Когда вы вызываете malloc
или new
, в куче выделяется блок памяти. Вам дается указатель для доступа к этому блоку памяти.
int main(int argc, char * argv[])
{
int * a = new int;
return *a;
}
Здесь в куче выделяется новое целое число памяти. Вы получаете указатель с именем a
, который указывает на эту память.
a
- это локальная переменная, и так оно и есть в «блоке» main
Конечно, использование динамически выделяемой памяти, похоже, тратит туда и сюда несколько байтов на указатели. Однако есть вещи, которые вы просто не можете (легко) сделать без динамического выделения памяти.
int main(int argc, char * argv[])
{
int * intarray = create_array();
return intarray[0];
}
int * create_array()
{
int intarray[5];
intarray[0] = 0;
return intarray;
}
Что здесь происходит? Вы «возвращаете массив» в create_array
. На самом деле вы возвращаете указатель, который просто указывает на часть «блока» create_array
, содержащего массив. Что происходит, когда возвращается create_array
? Его ящик уничтожается, и вы можете ожидать, что ваш массив будет поврежден в любой момент.
Вместо этого используйте динамически выделяемую память.
int main(int argc, char * argv[])
{
int * intarray = create_array();
int return_value = intarray[0];
delete[] intarray;
return return_value;
}
int * create_array()
{
int * intarray = new int[5];
intarray[0] = 0;
return intarray;
}
Поскольку возврат функции не изменяет кучу, ваш драгоценный интаррей
уцелел. Не забудьте удалить []
его, когда закончите.
Память, выделенная «новым», оказывается в куче.
Память, выделенная в функции, находится внутри функции, в которой функция помещается в стек.
Прочтите о стеке vs распределение кучи здесь: http://www-ee.eng.hawaii.edu/~tep/EE160/Book/chap14/subsection2.1.1.8.html
Память, выделенная с помощью оператора new, извлекается из раздела памяти, называемого «куча», в то время как статические выделения для переменных используют раздел памяти, совместно используемый с вызовами процедур / функций («стек»).
Вам нужно только беспокоиться о распределения динамической памяти, которые вы сделали сами с помощью новых переменных, которые известны во время компиляции (определены в исходном коде), автоматически освобождаются в конце своей области (конец функции / процедуры, блок, ...).
Большая разница между «динамической» и «обычной» памятью довольно хорошо отражена в самом вопросе.
Динамическая память не очень хорошо поддерживается C ++.
Когда вы используете динамическую память, вы несете за нее полную ответственность. Вы должны выделить это. Когда вы забудете это сделать и попытаетесь получить к нему доступ, бросив указатель, у вас будет много отрицательных сюрпризов. Также вам нужно освободить память - а когда вы ее каким-либо образом забудете, вас ждет еще больше сюрпризов. Такие ошибки относятся к числу наиболее трудных для поиска ошибок в программах C / C ++.
Вам нужен дополнительный указатель, так как вам каким-то образом нужен доступ к вашей новой памяти. Некоторая память (динамическая или нет) - это прежде всего то, с чем не может справиться язык программирования. У вас должен быть к нему доступ. Это делается с помощью переменных. Но переменные в таких языках, как C ++, хранятся в «обычной» памяти. Итак, вам нужны «указатели» - указатели - это форма косвенного обращения, которая говорит: «Нет, я не то значение, которое вы ищете, но я указываю на него». Указатели - единственная возможность в C ++ для доступа к динамической памяти.
Напротив, к «обычной» памяти можно обращаться напрямую, выделение и освобождение выполняется автоматически самим языком.
Динамическая память и указатели - самый большой источник для проблемы в C ++ - но это также очень мощная концепция - если вы все сделаете правильно, вы сможете сделать гораздо больше, чем с обычной памятью.
Это также причина того, что многие библиотеки имеют функции или целые модули для работы с с динамической памятью. Пример auto_ptr также упоминался в параллельном ответе, который пытается решить проблему, эта динамическая память должна быть надежно освобождена в конце метода.
Обычно вы будете использовать динамическую память только в тех случаях, когда она вам действительно нужна. Вы не будете использовать его, чтобы иметь единственную целочисленную переменную, но чтобы иметь массивы или создавать большие структуры данных в памяти.
Динамическая память находится в куче, а не в стеке. Время жизни динамической памяти - от времени выделения до момента освобождения. С локальными переменными их время жизни ограничено функцией / блоком, в котором они определены.
Что касается вашего вопроса об использовании памяти в функции, в вашем примере память для ваших локальных переменных будет освобождена в конце функции . Однако, если память была выделена динамически с помощью new
, она не будет автоматически удалена, и вы будете нести ответственность за явное использование delete
для освобождения памяти.
Что касается автоматической памяти. управления, Стандартная библиотека C ++ предоставляет для этого auto_ptr.