Подтвердить что ты не робот

Лучший способ обработки выделения памяти в C?

Я думаю, что у меня есть хорошее понимание того, как обращаться с памятью на С++, но делать это на C отличается. Я немного отключаюсь.

В С++ у меня есть конструкторы и деструкторы, у меня есть довольно простое новое и удаление, и я знаю, как его инкапсулировать с помощью RAII, используя интеллектуальные указатели и внутри классов.

Однако в C я не могу обрабатывать malloc и освобождать то же самое. Я не знаю, как скрыть их и как автоматизировать вещи. Все, что я могу понять, это использовать функции для инициирования и уничтожения моих указателей. Но как мне структурировать обработку моей памяти?

При написании этого я понял, что это больше вопрос о том, как я понимаю поток С, чем что-либо еще, но один вопрос за раз.

Edit: Спасибо за ответы, но мне нужно перефразировать себя.

Когда я говорю, что я использую RAII и интеллектуальные указатели для С++, я не хочу того же для C, я знаю, что это не то же самое. Но то, как я обрабатываю выделение памяти в С++, связано с этими методами.

Например, в моих классах я динамически добавляю и уничтожаю память, которую использует мой класс. Таким образом, я могу добиться своего рода инкапсуляции, мне не нужно знать, когда/как/почему класс обрабатывает эту память, это просто так. Это означает, что я могу "спрятать" нижнюю память и просто сосредоточиться на нескольких "больших" классах.

Что я хочу знать, это то, что лучше всего подходит для обработки памяти в C? Для меня нет классов с конструкторами/деструкторами. Хорошо ли выделять память в начале функции или использовать функцию, которая создает ее для меня? И как мне их снова освободить?

Это широкие вопросы, и они отличаются от ситуации к ситуации, но как вы предпочитаете ее обрабатывать? Какие советы и уроки вы можете дать?

4b9b3361

Ответ 1

Часть путаницы состоит в том, что в C. ct malloc и free они сложнее схожи с new и delete: malloc выделяет новую память и возвращает указатель на эту память. free снова делает эту память доступной, , пока память, выделенная с помощью malloc. В противном случае, это просто делает хэш некоторой части памяти. Это не важно.

Важная вещь с malloc/free - это принять решение и последовательно поддерживать дисциплинированное использование. Вот несколько советов:

ВСЕГДА проверяем возвращенный указатель из malloc для NULL

if((p = (char *) malloc(BUFSIZ)) == NULL {
   /* then malloc failed do some error processing. */
}

Для обеспечения безопасности ремней и подтяжек установите указатель на NULL после его освобождения.

free(p);
p = NULL ;

попробуйте malloc и освободите фрагмент памяти в пределах одной области, если это возможно:

 {  char * p ;
   if((p = malloc(BUFSIZ)) == NULL {
       /* then malloc failed do some error processing. */
   }

 /* do your work. */

   /* now you're done, free the memory */

   free(p);
   p = NULL ;  /* belt-and suspenders */
 }

Если вы не можете, дайте понять, что то, что вы возвращаете, это malloc 'ed memory, поэтому вызывающий может освободить его.

 /* foo: do something good, returning ptr to malloc memory */
 char * foo(int bar) {
     return (char *) malloc(bar);
 }

Ответ 2

Во время написания этого я понял это больше вопрос обо мне понимая поток С, чем все остальное, но один вопрос на время.

Честно говоря, вы должны прочитать K & R, если вы этого не сделали.

Ответ 3

К сожалению, существуют ограниченные стратегии для автоматизации распределения памяти и освобождения памяти в C. Компилятор С++ генерирует много кода за кулисами для вас - он отслеживает каждую переменную в стеке и гарантирует, что вызван соответствующий деструктор когда стек очищается. На самом деле это довольно сложный тип генерации кода, особенно когда вы добавляете исключения в микс.

C, с другой стороны, намного проще, поэтому его иногда называют "языком ассемблера высокого уровня". C не имеет какого-либо механизма, гарантирующего, что вызывается конкретный бит кода при выходе из функции или сбрасывается переменная из стека, поэтому вам нужно отслеживать каждый бит памяти, которую вы выделяете, и каждый файл или сеть вы открываете и очищаете их в соответствующей точке. Нет практического способа создания автоматического интеллектуального указателя в C.

Одна концепция, на которую вы должны обратить внимание, - это "пулы памяти". В основном, скорее, чтобы попытаться отследить каждый отдельный блок памяти, который вы выделяете, вы создаете пул, выполняете часть работы, помещая каждый блок памяти, который вы выделяете в пул, а затем освобождаете весь пул, когда закончите. Вы торгуете немного производительности и контроля здесь, чтобы облегчить когнитивную нагрузку на программиста, но большую часть времени это того стоит.

Вы должны заглянуть в проект Apache Portable Runtime. У них есть библиотека пула памяти (документы http://apr.apache.org/docs/apr/1.3/group__apr__pools.html). Если APR слишком много для вас, чтобы погрузиться в него, вы можете реализовать очень простой пул памяти, используя три функции и структуру связанных списков. Псевдокод будет примерно таким:

struct Pool {
  void* memoryBlock;
  struct Pool *next;
}

struct Pool *createPool(void) {
  /* allocate a Pool and return it */
}

void addToPool(struct Pool *pool, void *memoryBlock) {
  /* create a new Pool node and push it onto the list */
}

void destroyPool(struct Pool *pool) {
  /* walk the list, free each memory block then free its node */
}

Использование пула - это примерно так:

int main(void) {
  struct Pool *pool = createPool();
  /* pool is empty */

  doSomething(pool);

  /* pool full of crap, clean it up and make a new one */
  destroyPool(pool);
  pool = createPool();
  /* new pool is empty */

  doMoreStuff(pool);
  destroyPool(pool);

  return 0;
}

Ответ 4

Печальная истина заключается в том, что C не предназначен для инкапсуляции всех этих проблем управления памятью.

Если вы посмотрите на довольно качественные API, такие как POSIX, вы увидите, что общий шаблон состоит в том, что вы передаете указатель на указатель на функцию, которая затем выделяет память, и что вы позже передаете ее снова которая уничтожает его.

Это не обязательно элегантный, но я не думаю, что есть много способов сделать его действительно элегантным, не моделируя ООП в C.

Ответ 5

Ну, в C вам нужно все вручную управлять памятью, как вы уже обнаружили. Это не должно быть неожиданностью.

Ответ 6

Я не знаю, как скрыть их и как автоматизировать вещи.

C и С++ - разные языки. Теперь скажи, что сто раз для себя. Будьте громкими.

Что вы имеете в виду, скрывая? Что вы подразумеваете под автоматизацией? Можете ли вы опубликовать несколько примеров? Зачем вам нужно скрывать и/или автоматизировать.

Хорошие места онлайн для начала с распределением памяти C:

Ответ 7

Один из способов "скрыть" выделение и выделение памяти - передать его в пользовательские контейнеры. Пропустите контейнер нераспределенным объектом. Пусть это беспокоится о malloc, и когда я удаляю объект, пусть он беспокоится о бесплатном. Конечно, это работает, только если вы только сохраняете объект в одном контейнере. Если у меня есть ссылки на объекты по всему месту, я создам эквивалент методов конструктора и деструктора с синтаксисом c:

 glob* newGlob(); 
 void freeGlob(glob* g);

(по объекту я имею в виду все, на что вы указывали бы - не объекты С++).

Ответ 8

Там вы можете многое сделать, чтобы сделать вашу жизнь проще. Кажется, вы уже столкнулись с идеей создания фабрик/конструкторов для ваших C-объектов. Это хорошее начало следует за этим.

Некоторые другие идеи для рассмотрения.

  • не соглашаются на стандартный malloc/free. пойдите в поисках лучшего, который был раскрыт или написал тот, который позволяет использовать память объектов, которые вы создаете. Кроме того, мы говорим C здесь, вы собираетесь перезаписывать свои объекты бесплатно больше и один раз и забыть их освобождать, поэтому создайте некоторую поддержку отладки в своем malloc. писать собственное не сложно, если вы не можете найти тот, который соответствует вашим потребностям.

  • используйте более одной кучи. используйте один класс для каждого класса, который вы создаете, используйте временные кучи, если знаете, что у вас будет большое количество связанных между собой временных объектов, это уменьшает фрагментацию памяти и позволяет вам управлять памятью в соответствии с использованием.

  • Посмотрите на стратегии, такие как Objective-C пулы

  • Если вы считаете, что понимаете, как работает С++, то добавление поведения конструктора в распределение памяти в объекте factory не так сложно сделать, и использование пользовательского встроенного бесплатного может затем предоставить вам возможность вызвать деструктор на объекте, свободном от восстановления некоторых из поведения С++, которое вам понравилось

Ответ 9

Обычный способ

MyType *ptr = malloc(array_size * sizeof *ptr);

Но если вы хотите быть совместимым с С++, сделайте

MyType *ptr = (MyType*) malloc(array_size * sizeof *ptr);

Вы также можете сделать макрос

#define MALLOC( NUMBER, TYPE ) ( TYPE * ) malloc( NUMBER * sizeof( TYPE ) )
MyType *ptr = MALLOC(10, MyType);

Конечно, без RAII, убедитесь, что когда-нибудь у вас будет

free(ptr);

Ответ 10

Я не совсем уверен, что вы спрашиваете, но C довольно просто:

struct Foo *f0 = malloc(sizeof(*f));   // alloc uninitialized Foo struct
struct Foo *f1 = calloc(1,sizeof(*f)); // alloc Foo struct cleared to all zeroes

//You usually either want to clear your structs using calloc on allocation, or memset. If 
// you need a constructor, just write a function:
Foo *Foo_Create(int a, char *b)
{
   Foo *r = calloc(1,sizeof(*r));
   r->a = a;
   r->b = strdup(b);
   return r;
}

Here is a simple C workflow with arrays:
struct Foo **foos = NULL;
int n_foos = 0;
...
for(i = 0; i < n_foos; ++i)
{
   struct Foo *f = calloc(1,sizeof(*f));
   foos = realloc(foos,sizeof(*foos)*++n_foos); // foos before and after may be different
   foos[n_foos-1] = f;
}

Если вам интересно, вы можете написать макросы, чтобы помочь:

#define MALLOCP(P) calloc(1,sizeof(*P)) // calloc inits alloc'd mem to zero

Несколько пунктов:

  • malloc, calloc, realloc и т.д. все используют free(), поэтому управлять этими вещами легко. Просто будьте последовательными.
  • Производительность для mallocs может быть медленной. Кто-то разместил ссылку на это выше. В эти дни быстрые многопоточные распределения являются ключевыми, см. Tcmalloc et al. Вы, вероятно, не должны беспокоиться об этом.
  • в современной архитектуре виртуальной памяти malloc почти никогда не сбой, если вы не находитесь в виртуальном адресном пространстве. Если это произойдет, переключитесь на 64 бит;)
  • Убедитесь, что вы используете систему, которая имеет проверку границ, стирает бесплатные значения, отслеживание утечки и все эти хорошие вещи (см. valgrind, win32 debug heap и т.д.).

Ответ 11

Я знаю, что это старый пост, но на самом деле не было много полного ответа на передовую практику с точки зрения стиля, который, по моему мнению, является тем, что действительно нужно оператору, поэтому вот мое выделение памяти на C Примечание. Я больше человек С++, из-за которого мои мысли исходят из этого отношения.

Часто бывает полезно узнать, назначен ли ваш указатель, поэтому всегда указывайте NULL указателю при его объявлении. Вы также можете создать себе безопасную бесплатную функцию, которая освобождает память, а затем присваивает ей NULL, поэтому вам не нужно беспокоиться.

Если вы выделяете память в одном файле C, вы должны освободить ее в том же файле. Это, пожалуй, более ограничительный, чем необходимо, однако, если вы пишете библиотеку, вы должны определенно освободить любую память в своей библиотеке, которая является malloc'd в вашей библиотеке. Это связано с тем, что на Windows dll есть другая куча для exe, поэтому mallocing memory в dll и освобождение ее в exe искажает вашу кучу.

По расширению и ради симметрии это означает, что если у вас есть функция, которая возвращает указатель на выделенную память, тогда у вас должна быть функция, которая освобождает эту память. Вот почему во многих библиотеках есть функция инициализации, которая возвращает указатель на некоторые данные (обычно передаваемые как void *), а затем функцию очистки, которая освобождает ресурсы библиотеки. Если вы можете malloc и освобождаться внутри одной и той же функции, это хорошо, так как вам легко следить за вещами.

Не пытайтесь выделить всю память в начале функции, а затем освободите ее в конце. Это просто означает, что если вы хотите вернуть часть пути через функцию, вы должны освободить всю память, тогда как если вы будете malloc и свободная память, когда вы пойдете, у вас будет меньше указателей на бесплатную.

Если у вас часто есть функции, которые выделяют много указателей, тогда рассмотрите создание и массив, который содержит указатели на все ваши указатели в начале функции, а затем функцию, которая освобождает их всех. Это спасет вас от неизбежного "я вернусь и уберу утечку памяти позже", если вы хотите вернуть среднюю функцию.

Концепция фабрик полезна. A factory будет функцией, которая будет хранить память для структуры, назначает указатель на функцию struct, инициализирует ее переменные и затем возвращает указатель на нее. Если первый из них был деструктором или массивом определенных функций, то вы можете иметь общую функцию уничтожения, которая может вызвать любой деструктор структуры, а затем освободить память структуры. Вы также можете скрыть некоторые внутренние детали класса, имея различное определение структуры внутри и снаружи. COM построен на этих принципах.

Итак, это только то, как я смотрю на память в C. Это не так элегантно, как на С++, но поскольку вы полагаетесь на людей, чтобы справиться с этим, есть стратегии, подобные выше, которые могут сделать вещи простыми насколько это возможно для них.

Обратите внимание, что всегда есть исключения для каждого правила - это то, о чем я думаю, когда я использую C. Я уверен, что у других людей есть другие идеи.

Фил

Ответ 12

Вы можете подумать, что то, что я говорю, странно, но нет большой разницы между С++ и C. C имеет RAII. RAII не относится только к С++.

Только у вас должно быть достаточно дисциплины для управления.

Класс С++:

class foo {
public:
   ofstream ff;
   int x,y;
   foo(int _x) : x(_x),y(_x){ ff.open("log.txt"); }
   void bar() { ff<<x+y<<endl; }
};

int main()
{
   auto_ptr<foo> f(new foo);
   f->bar();
}

Объект C

typedef struct FOO {
    FILE *ff;
    int x,y;
} *foo_t;

foo_t foo_init(int x)
{
   foo_t p=NULL;
   p=malloc(sizeof(struct FOO)); // RAII
   if(!p) goto error_exit;
   p->x=x; p->y=x;
   p->ff=fopen("log.txt","w");   // RAII
   if(!p->ff) goto error_exit;
   return p;
error_exit:   // ON THROW
   if(p) free(p);
   return NULL;
}

void foo_close(foo_t p)
{
   if(p) fclose(p->ff);
   free(p);
}

void foo_bar(foo_t p)
{
   fprintf(p->ff,"%d\n",p->x+p->y);
}

int main()
{
  foo_t f=foo_init(1);
  if(!f) return 1;
  foo_bar(f);
  foo_close(f);
  return 0;
}