Как зайти в Даркнет?!
25th January, 01:11
8
0
Как в tkinter из поля ввода Entry получить значение в одну переменную и обновить строку кнопкой, затем получить ещё одно введённое значение и затем сложить их. Ниже пример кода
21st July, 19:00
898
0
Программа, которая создает фейковые сервера в поиске игровых серверов CS 1.6 Steam
21st March, 17:43
950
0
Очень долго работает Update запрос Oracle
27th January, 09:58
916
0
не могу запустить сервер на tomcat HTTP Status 404 – Not Found
21st January, 18:02
907
0
Где можно найти фрилансера для выполнения поступающих задач, на постоянной основе?
2nd December, 09:48
940
0
Разработка мобильной кроссплатформенной военной игры
16th July, 17:57
1725
0
период по дням
25th October, 10:44
3957
0
Пишу скрипты для BAS только на запросах
16th September, 02:42
3722
0
Некорректный скрипт для закрытия блока
14th April, 18:33
4614
0
прокидывать exception в блоках try-catch JAVA
11th March, 21:11
4382
0
Помогите пожалуйста решить задачи
24th November, 23:53
6087
0
Не понимаю почему не открывается детальное описание продукта
11th November, 11:51
4352
0
Нужно решить задачу по программированию на массивы
27th October, 18:01
4398
0
Метода Крамера С++
23rd October, 11:55
4309
0
помогите решить задачу на C++
22nd October, 17:31
4002
0
Помогите решить задачу на python с codeforces
22nd October, 11:11
4492
0
Python с нуля: полное руководство для начинающих
18th June, 13:58
2599
0
C Управление Памятью
Я всегда слышал, что в C году вы должны действительно следить за тем, как вы управляете памятью. И я все еще начинаю учиться C, но до сих пор мне не приходилось делать никаких действий, связанных с управлением памятью.. Я всегда представлял себе, что мне придется освобождать переменные и делать всевозможные уродливые вещи. Но, похоже, это не так.
Может ли кто-нибудь показать мне (с примерами кода) пример того, когда вы должны были бы сделать некоторые "memory management" ?
Есть два места, где переменные могут быть помещены в память. Когда вы создаете такую переменную:
int a;
char c;
char d[16];
Переменные создаются в "стеке". Переменные стека автоматически освобождаются, когда они выходят за пределы области видимости (то есть когда код больше не может достичь их). Вы можете услышать, что они называются переменными "automatic", но это вышло из моды.
Многие начинающие примеры будут использовать только переменные стека.
Стек хорош тем, что он автоматический, но он также имеет два недостатка: (1) компилятор должен заранее знать, насколько велики переменные, и (Б) пространство стека несколько ограничено. Например: в Windows, в разделе параметры по умолчанию для компоновщика Microsoft, стек имеет значение 1 MB, и не все из них доступны для ваших переменных.
Если вы не знаете во время компиляции, насколько велик ваш массив, или если вам нужен большой массив или структура, вам нужно "plan B".
План Б называется "куча". Обычно вы можете создавать переменные такого размера, какой позволит вам операционная система, но вы должны сделать это сами. Более ранние публикации показали вам один способ, которым вы можете это сделать, хотя есть и другие способы:
int size;
// ...
// Set size to some value, based on information available at run-time. Then:
// ...
char *p = (char *)malloc(size);
(Обратите внимание, что переменные в куче обрабатываются не напрямую, а через указатели)
Как только вы создаете переменную кучи, проблема заключается в том, что компилятор не может сказать, когда вы закончите с ней, поэтому вы теряете автоматическое освобождение. Вот тут-то и появляется "manual releasing", о котором вы говорили. Теперь ваш код отвечает за то, чтобы решить, когда переменная больше не нужна, и освободить ее, чтобы память можно было использовать для других целей. Для случая, описанного выше, с:
free(p);
Что делает этот второй вариант "nasty business" тем, что не всегда легко узнать, когда переменная больше не нужна. Забыв освободить переменную, когда она вам не нужна, ваша программа будет потреблять больше памяти, чем ей нужно. Эта ситуация называется a "leak". Память "leaked" не может быть использована ни для чего, пока ваша программа не закончится и OS не восстановит все свои ресурсы. Еще более неприятные проблемы возможны, если вы по ошибке освободите переменную кучи, прежде чем фактически закончите с ней.
В C и C++ вы несете ответственность за очистку ваших переменных кучи, как показано выше. Однако существуют языки и среды, такие как Java и .NET, такие как C#, которые используют другой подход, когда куча очищается сама по себе. Этот второй метод, называемый "сборкой мусора", гораздо проще для разработчика, но вы платите штраф в накладных расходах и производительности. Это равновесие.
(Я пропустил много деталей, чтобы дать более простой, но, надеюсь, более уравновешенный ответ)
Вот вам пример. Предположим, у вас есть функция strdup(), которая дублирует строку:
char *strdup(char *src)
{
char * dest;
dest = malloc(strlen(src) + 1);
if (dest == NULL)
abort();
strcpy(dest, src);
return dest;
}
И ты называешь это так:
main()
{
char *s;
s = strdup("hello");
printf("%s\n", s);
s = strdup("world");
printf("%s\n", s);
}
Вы можете видеть, что программа работает, но вы выделили память (через malloc), не освобождая ее. Вы потеряли указатель на первый блок памяти, когда вызвали strdup во второй раз.
Это не имеет большого значения для такого небольшого объема памяти, но рассмотрим этот случай:
for (i = 0; i < 1000000000; ++i) /* billion times */
s = strdup("hello world"); /* 11 bytes */
Теперь вы израсходовали 11 гигабайт памяти (возможно, больше, в зависимости от вашего менеджера памяти), и если вы не разбились, ваш процесс, вероятно, работает довольно медленно.
Чтобы исправить, вам нужно вызвать free() для всего, что получается с malloc() после того, как вы закончите его использовать:
s = strdup("hello");
free(s); /* now not leaking memory! */
s = strdup("world");
...
Надеюсь, этот пример поможет!
Вы должны сделать "memory management", если хотите использовать память в куче, а не в стеке. Если вы не знаете, как большой массив сделать до времени выполнения, то вы должны использовать кучу. Например, вы можете хранить что-то в строке, но не знаете, насколько большим будет ее содержимое, пока программа не будет запущена. В таком случае вы бы написали что-нибудь вроде этого:
char *string = malloc(stringlength); // stringlength is the number of bytes to allocate
// Do something with the string...
free(string); // Free the allocated memory
Я думаю, что самый краткий способ ответить на этот вопрос-рассмотреть роль указателя в C. Указатель-это легкий, но мощный механизм, который дает вам огромную свободу ценой огромной возможности выстрелить себе в ногу.
В C ответственность за то, чтобы ваши указатели указывали на память, которой вы владеете, лежит на вас и только на вас. Это требует организованного и дисциплинированного подхода, если только вы не откажетесь от указателей, что затрудняет написание эффективной C.
Опубликованные ответы на сегодняшний день сосредоточены на автоматическом (стековом) и кучном распределении переменных. Использование выделения стека действительно создает автоматически управляемую и удобную память, но в некоторых обстоятельствах (большие буферы, рекурсивные алгоритмы) это может привести к ужасающей проблеме переполнения стека. Точное знание того, сколько памяти вы можете выделить в стеке, очень зависит от системы. В некоторых встроенных сценариях несколько десятков байт может быть вашим пределом, в некоторых настольных сценариях вы можете безопасно использовать мегабайты.
Выделение кучи в меньшей степени присуще языку. Это в основном набор библиотечных вызовов, который предоставляет вам право собственности на блок памяти заданного размера, пока вы не будете готовы вернуть его ('free'). Это звучит просто, но связано с несказанным горем программиста. Эти проблемы просты (освобождение одной и той же памяти дважды или вообще не происходит [утечка памяти], выделение недостаточно памяти [переполнение буфера] и т. д.), Но их трудно избежать и отладить. Высокодисциплинированный подход является абсолютно обязательным на практике, но, конечно, язык на самом деле не предписывает его.
Я хотел бы упомянуть еще один тип распределения памяти, который был проигнорирован другими публикациями. Можно статически выделить переменные, объявив их вне любой функции. Я думаю, что в целом этот тип распределения получает плохой рэп, потому что он используется глобальными переменными. Однако нет ничего, что говорит, что единственный способ использовать память, выделенную таким образом, - это как недисциплинированная глобальная переменная в беспорядке спагетти-кода. Метод статического распределения может быть использован просто для того, чтобы избежать некоторых подводных камней кучных и автоматических методов распределения. Некоторые программисты C с удивлением узнают, что большие и сложные программы C embedded и games были построены без использования выделения кучи вообще.
Здесь есть несколько замечательных ответов о том, как выделять и освобождать память, и, на мой взгляд, более сложной стороной использования C является обеспечение того, что вы используете только выделенную память - если это не сделано правильно, то в конечном итоге вы получаете двоюродный брат этого сайта - переполнение буфера - и вы можете перезаписывать память, которая используется другим приложением, с очень непредсказуемыми результатами.
Образец:
int main() {
char* myString = (char*)malloc(5*sizeof(char));
myString = "abcd";
}
На этом этапе вы выделили 5 байт для myString и заполнили его "abcd\0 "(строки заканчиваются на null-\0). Если ваше распределение строк было
myString = "abcde";
Вы бы назначили "abcde" в 5 байтах, которые были выделены вашей программе, и символ trailing null был бы помещен в конце этого - часть памяти, которая не была выделена для вашего использования и могла бы быть свободной, но могла бы также использоваться другим приложением - это критическая часть управления памятью, где ошибка будет иметь непредсказуемые (а иногда и неповторимые) последствия.
Следует помнить, что всегда следует инициализировать указатели на NULL, так как неинициализированный указатель может содержать псевдослучайный допустимый адрес памяти,который может привести к ошибкам указателя. Применяя указатель для инициализации с помощью NULL, вы всегда можете перехватить, если вы используете этот указатель без его инициализации. Причина заключается в том, что операционные системы "wire" виртуальный адрес 0x00000000 для общей защиты исключения для ловушки null использования указателя.
Кроме того, вы можете использовать динамическое выделение памяти, когда вам нужно определить огромный массив, скажем int[10000]. Вы не можете просто положить его в стопку, потому что тогда, хм... вы получите переполнение стека.
Другим хорошим примером может быть реализация структуры данных, например связанного списка или бинарного дерева. У меня нет примера кода, чтобы вставить его сюда, но вы можете легко погуглить его.
(Я пишу, потому что чувствую, что ответы пока не совсем на месте.)
Причина, по которой вы должны упомянуть об управлении памятью, заключается в том, что у вас есть проблема / решение, которое требует создания сложных структур. (Если ваши программы отказывают, если вы выделяете много места в стеке сразу, это ошибка.) Как правило, первая структура данных, которую вам нужно будет изучить, - это какой-то список . Вот один из них, связанный с моей головой.:
typedef struct listelem { struct listelem *next; void *data;} listelem;
listelem * create(void * data)
{
listelem *p = calloc(1, sizeof(listelem));
if(p) p->data = data;
return p;
}
listelem * delete(listelem * p)
{
listelem next = p->next;
free(p);
return next;
}
void deleteall(listelem * p)
{
while(p) p = delete(p);
}
void foreach(listelem * p, void (*fun)(void *data) )
{
for( ; p != NULL; p = p->next) fun(p->data);
}
listelem * merge(listelem *p, listelem *q)
{
while(p != NULL && p->next != NULL) p = p->next;
if(p) {
p->next = q;
return p;
} else
return q;
}
Естественно, вы хотели бы иметь несколько других функций, но в основном это то, для чего вам нужно управление памятью. Я должен отметить, что есть ряд трюков, которые возможны с управлением памятью "manual", например,
- Используя тот факт, что malloc гарантированно (по стандарту языка) возвращает указатель, кратный 4,
- выделение дополнительного пространства для какой-то своей зловещей цели,
- создание памяти с бассейном..
Получите хороший отладчик. .. Удачи вам!
В C году у вас фактически есть два разных варианта выбора. Во-первых, вы можете позволить системе управлять памятью за вас. Кроме того, вы можете сделать это самостоятельно. Как правило, вы хотели бы придерживаться первого как можно дольше. Однако автоматически управляемая память в C чрезвычайно ограничена, и во многих случаях вам потребуется вручную управлять памятью, например:
a. вы хотите, чтобы переменная пережила функции, и вы не хотите иметь глобальную переменную. экс:
struct pair{
int val;
struct pair *next;
}
struct pair* new_pair(int val){
struct pair* np = malloc(sizeof(struct pair));
np->val = val;
np->next = NULL;
return np;
}
б. вы хотите иметь динамически выделяемую память. Наиболее распространенным примером является массив без фиксированной длины:
int *my_special_array; my_special_array = malloc(sizeof(int) * number_of_element); for(i=0; ic. You want to do something REALLY dirty. For example, I would want a struct to represent many kind of data and I don't like union (union looks soooo messy):
struct data{ int data_type; long data_in_mem; }; struct animal{/*something*/}; struct person{/*some other thing*/}; struct animal* read_animal(); struct person* read_person(); /*In main*/ struct data sample; sampe.data_type = input_type; switch(input_type){ case DATA_PERSON: sample.data_in_mem = read_person(); break; case DATA_ANIMAL: sample.data_in_mem = read_animal(); default: printf("Oh hoh! I warn you, that again and I will seg fault your OS"); }
Видите ли, длинное значение достаточно для удержания ANYTHING. Просто не забудьте освободить его, или вы WILL пожалеете. Это один из моих любимых трюков, чтобы весело провести время в C: D.
Однако, как правило, вы хотели бы держаться подальше от ваших любимых трюков (T___T). Вы WILL сломаете свой OS, рано или поздно, если будете использовать их слишком часто. Пока вы не используете *alloc и free, можно с уверенностью сказать, что вы все еще девственны, и что код все еще выглядит красиво.
@ Тэд Персиваль :
...
вам не нужно приводить возвращаемое значение malloc().
@ Тэд Персиваль :
...
вам не нужно приводить возвращаемое значение malloc().
Вы, конечно, правы. Я считаю, что это всегда было правдой, хотя у меня нет копии K&R , чтобы проверить.
Мне не нравится много неявных преобразований в C, поэтому я обычно использую приведения, чтобы сделать "magic" более заметным. Иногда это помогает читабельности, иногда нет, а иногда это приводит к тому, что компилятор ловит молчаливую ошибку. Тем не менее, у меня нет твердого мнения по этому поводу, так или иначе.
Это особенно вероятно, если ваш компилятор понимает комментарии в стиле C++.
Да... ты застал меня там. Я провожу гораздо больше времени в C++, чем в C. Спасибо, что заметил это.
Один минус, который нужно добавить, заключается в том, что указатели на стек больше не действительны, когда функция возвращает, поэтому вы не можете вернуть указатель на переменную стека из функции. Это распространенная ошибка и основная причина, по которой вы не можете обойтись только переменными стека. Если ваша функция должна возвращать указатель, то вы должны malloc и иметь дело с управлением памятью.
Конечно. Если вы создаете объект, который существует вне области, в которой вы его используете. Вот надуманный пример (имейте в виду, что мой синтаксис будет выключен; мой C ржавый, но этот пример все равно проиллюстрирует концепцию):
class MyClass
{
SomeOtherClass *myObject;
public MyClass()
{
//The object is created when the class is constructed
myObject = (SomeOtherClass*)malloc(sizeof(myObject));
}
public ~MyClass()
{
//The class is destructed
//If you don't free the object here, you leak memory
free(myObject);
}
public void SomeMemberFunction()
{
//Some use of the object
myObject->SomeOperation();
}
};
В этом примере я использую объект типа SomeOtherClass в течение жизни MyClass. Объект SomeOtherClass используется в нескольких функциях, поэтому я динамически выделил память: объект SomeOtherClass создается при создании MyClass, используется несколько раз в течение жизни объекта, а затем освобождается после освобождения MyClass.
Очевидно, что если бы это был реальный код, то не было бы никакой причины (кроме возможного потребления стековой памяти) создавать myObject таким образом, но этот тип создания/уничтожения объектов становится полезным, когда у вас есть много объектов, и вы хотите точно контролировать, когда они создаются и уничтожаются. например, чтобы ваше приложение не засасывало 1 ГБ RAM за всю свою жизнь), и в оконной среде это в значительной степени обязательно, поскольку объекты, которые вы создаете (скажем, кнопки), должны существовать далеко за пределами области действия какой-либо конкретной функции (или даже класса).