LINUX.ORG.RU

Безопасные указатели в С и С++

 выстрелить в ногу


0

3

Привет всем.

Во время оно увлекался программированием на С и неожиданно наткнулся на непонятное мне в концепции языка место. Допустим у нас есть такой код:

type* foo()
{
	type *foo_ptr = (type*) malloc(TOO_LARGE_BUFFER);
	//.....Do it 
	return foo_ptr;
}
. Вопрос: будет ли после вызова этой функции возвращена память системе? Ведь насколько я понимаю в стеке окажутся и затем вытолкнутся всего 4 байта (в случае х86), а сама выделенная память может быть много больше по размеру. Как правильно проектировать подобный код?

Всем спасибо.

Ответ на: комментарий от Legioner

Управление будет в одном месте. Меньше вероятность забыть освободить память

С чего бы? Ты всё равно должен не забыть сделать 1 вызов free.

меньше дополнительной контекстной нагрузки на функцию.

Правда?

 struct foo *v = malloc(sizeof(struct foo));

 init_foo(v);
 /*...*/
 free(v);

против

 struct foo *v = new_foo();

 /*...*/
 free(v); // или, скорее, delete_foo(v)

Какая именно «контекстная нагрузка» большк во втором случае?

При желании легче поменять стратегию выделения - использовать свой аллокатор, выделять на стеке.

Для этого делается отдельная функция init_foo, которая ничего не выделяет, _но_ это не отменяет кошерности new_foo.

P.S. сравни:

struct foo *v = malloc(sizeof(struct bar));

и

struct foo *v = new_bar();

и подумай, какой вариант тебе ближе.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Кстати, можно и в сях свой деструктор сделать: написать обертку для malloc'а, которая будет добавлять указатель на выделяемую память в глобальный стек. А функция-деструктор будет пробегаться по всему стеку и для каждого указателя вызывать free.

Очень удобно, если в функции идет работа с большим количеством malloc'ов. Тут даже можно без goto, обычного для этого случая, обойтись: просто в случае ошибки вместо goto end_of_function писать delete_all(); return;.

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от tailgunner

С чего бы? Ты всё равно должен не забыть сделать 1 вызов free.

Проглядеть вызов free, если вызывается malloc сложнее, чем проглядеть вызов free, если вызывается foo().

Какая именно «контекстная нагрузка» большк во втором случае?

Тут да, цель функции по сути выделение памяти, проблем не будет. Всё читаемо. Если функция называется не new_foo(); а, например, wtfprint, то уже не так очевидно, что там за указатель возвращается и надо ли его удалять.

Для этого делается отдельная функция init_foo, которая ничего не выделяет, _но_ это не отменяет кошерности new_foo.

А в чём вообще смысл new_foo? Вместо двух понятных строк написать вызов функции из этих же двух строк, при этом в одном месте будет new_foo, в другом, где на стеке можно положить структуру, init_foo. Не вижу кошерности, только преумножение сущностей без необходимости. Я так понял, ты ещё и предлагаешь писать в одном месте delete_foo(foo_ptr), в другом free_foo(&foo), если для удаления struct foo надо сделать что-то бОльшее, чем free(foo_ptr).

и подумай, какой вариант тебе ближе.

Учитывая, что очень часто на стеке всё ложится без проблем и дёргать malloc смысла нет, первый вариант с init_foo мне ближе, т.к. код получается однороднее.

Legioner ★★★★★
()
Ответ на: комментарий от Eddy_Em

apache httpd насколько я знаю, для каждого запроса выделяет пул памяти, из которого и выделяется память во время выполнения запроса. А освобождается всё в самом конце освобождением пула. Тоже интересный вариант для некоторых случаев. Эту память можно вообще не удалять, в этом случае malloc получается очень быстрый ну и free один на запрос.

Legioner ★★★★★
()

ты же используешь Qt - возвращай QByteArray|QVariant|QString|.. и не беспокойся о памяти

x905 ★★★★★
()
Ответ на: комментарий от O02eg

так не использовать malloc нужно, а возвращать QString(«some string»)

x905 ★★★★★
()

Если ты такой ссыкливый и боишься указателей, то используй valgrind.

anonymous
()
Ответ на: комментарий от Kiborg

Хорошим правилом считается следующее: по возможности malloc() и free() должны быть в одном блоке. То есть кто память выделил, тот ее и освобождает. Поэтому в примере приведен не очень удачный код:

Глупостями разговариваешь. А если, например, здесь выделяется элемент двоичного дерева? Которому еще долго жить предстоит, и удалят его потом целиком.

anonymous
()
Ответ на: комментарий от anonymous

Да ладно, хороший детектор, что автор ни разу ничего серьёзного на сишечке не писал. Любые долгоживущие объекты ломают его уютный мирок на раз-два, а без них никуда.

kemm
()
Ответ на: комментарий от Kiborg

Простейший пример

stdio.h:

/* Define outside of namespace so the C++ is happy.  */
struct _IO_FILE;

__BEGIN_NAMESPACE_STD
/* The opaque type of streams.  This is the definition used elsewhere.  */
typedef struct _IO_FILE FILE;
__END_NAMESPACE_STD

tst.c:

...
FILE *fp = malloc(/* WHAT? */);

kemm
()
Ответ на: комментарий от Legioner

А в чём вообще смысл new_foo?

Смысл new_foo - «разместить объект и инициализировать его».

Вместо двух понятных строк написать вызов функции из этих же двух строк,

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

одном месте будет new_foo, в другом, где на стеке можно положить структуру, init_foo. Не вижу кошерности, только преумножение сущностей без необходимости.

Ты троллишь штоле?

Я так понял, ты ещё и предлагаешь писать в одном месте delete_foo(foo_ptr), в другом free_foo(&foo)

Ты понял не так. Деструктор foo должен быть один, но, если он сводится к free, можно писать просто free.

первый вариант с init_foo мне ближе

Ясно. Намеренно сделанную ошибку ты просто не заметил. ЧТД.

tailgunner ★★★★★
()
Ответ на: комментарий от anonymous

Хорошим правилом считается следующее: по возможности malloc() и free() должны быть в одном блоке

А если, например, здесь выделяется элемент двоичного дерева?

Значит, возможности расположить malloc и free в одном блоке нет.

tailgunner ★★★★★
()
Ответ на: комментарий от tailgunner

Естественно. Что чревато разрывом шаблона у таких вот нубов.

anonymous
()
Ответ на: комментарий от Eddy_Em

Да я и не говорю, что обязательно. Но чаще всего такое правило хорошо работает, и для начинающего программиста оно подходит. Конечно, есть случаи, когда применить его просто невозможно, и об этом я сказал:

(когда это возможно)

Наверняка есть случаи, когда правило можно применить, но лучше сделать по-другому. Но такие случаи достаточно редки, это скорее всего какая-то экзотика.

Kiborg ★★★
()
Ответ на: комментарий от Kiborg

Правило - говно. Грузить начинающих говноправилами просто подло. Руки отрывать таким «гуру».

anonymous
()

Во время оно

Это еще что такое?

P.S. Вы с vertexua не братья случайно?

anonymous
()
Ответ на: комментарий от LongLiveUbuntu

А в случае С++?

сам я на C++ писал мало. В пределах библиотеки алгоритмов на графах LEDA. В общем-то вполне норм. Но утверждать далее не решусь. Все таки я предпочитаю паскаль или пистон.

dikiy ★★☆☆☆
()
Ответ на: Простейший пример от kemm

С долгоживущими понятно что такое не прокатит. Я не понимаю, почему вам это не очевидно. Но если возникают вопросы, как лучше написать:

int * a = malloc ( ... );
get_array(a);
result = do_smth(a);
free(a);
или
int * a = get_array();
result = do_smth(a);
free(a);
я предпочту первый вариант, потому что следить за выделением и освобождением проще: где маллок, там и free. Вот и всё. Частый пример - буфер для промежуточных результатов. Нарисуйте что-нибудь, где из этих двух случаев лучше выбрать второй вариант. Не такой, где первый нельзя использовать, а такой, где можно, но лучше не.

Kiborg ★★★
()
Ответ на: комментарий от Kiborg

Нарисуйте что-нибудь, где из этих двух случаев лучше выбрать второй вариант.

int * a = get_array();
result = do_smth(a);
free_array(a);

Или вообще

int *a = get_array();
result = do_smth(a);
…
int *b = get_array();
do_smth(b);
…
int *c = get_array();
int *d = get_array();
do_smth(c, d);
…
free_arrays();

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от Kiborg

В названиях. Можно еще обозвать array_alloc, array_free.

А второй вариант хорош тем, что вне зависимости от того, сколько раз вы malloc вызовете (вдруг где-то не получится, или цикл сделаете, или еще что) все объекты будут подчищены. Оберточка пишется достаточно легко.

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от Kiborg

если уж не проверять результат malloc, то можно и динамический массив использовать, будет и проще и быстрее

wota ★★
()
Ответ на: комментарий от Eddy_Em

Про оберточку я не понял: как в си написать такую free_arrays(), чтобы она сама освобождала a,b,c,d? Макросами? Но тогда при добавлении еще одного int * f = malloc(...) мне надо вспомнить, что у меня где-то там макрос был. Передавать в параметрах функции? Но тогда у нее получается переменное число аргументов - лишняя морока. В общем, не оценил пока.

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

Kiborg ★★★
()
Ответ на: комментарий от wota

Ну я уж не стал выписывать всё подробно, чтобы простыню не разворачивать ) можно и проверку написать, только дело-то не в ней.

Kiborg ★★★
()
Ответ на: комментарий от Kiborg

как в си написать такую free_arrays(), чтобы она сама освобождала a,b,c,d?

Я уже в начале писал: например, помещать выделяемые указатели в стек простенькой самопальной push. А потом самопальной же pop поочередно их извлекать и free.

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от Eddy_Em

а если без free_arrays, только с free_array, то можно ещё и поиск утечек памяти организовать автоматический.

anonymous
()

Смартпоинтеры C++ решают. Тут уже советовали unique_ptr:

std::unique_ptr<T[]> foo() { return std::unique_ptr<T[]>(new T[TOO_LARGE_BUFFER]); }
Хотя лучше std::vector.

anonymous
()
Ответ на: комментарий от Eddy_Em

Я уже в начале писал: например, помещать выделяемые указатели в стек простенькой самопальной push. А потом самопальной же pop поочередно их извлекать и free.

Чем принципиально будет отличается набор вызовов push/pop от malloc/free?

anonymous
()
Ответ на: комментарий от Kiborg

Еще как вариант у функций возвращающих дин.память брать имена с окончанием N например fooN

следовательно, мы помним, что она вернула дин. указатель и его надо очистить.

Dudraug ★★★★★
()
Ответ на: комментарий от anonymous

push/pop тоже будут через malloc/free, зато функция подчистки, вызывая подряд pop вплоть до NULL, высвободит всю память, выделенную push

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от anonymous

Это тоже обертки. При чем здесь ассемблер? Аккумулятора же не хватит!

Eddy_Em ☆☆☆☆☆
()
Ответ на: комментарий от Kiborg

Нарисуйте что-нибудь, где из этих двух случаев лучше выбрать второй вариант

Например, когда у нас есть структура, и нам нужно выделять память и под нее, и под некоторые ее поля:

struct foo
{
    int int_field;
    char *char_field;
};

#define ENSURE(COND, LABEL)                     \
    do                                          \
    {                                           \
        if(!(COND))                             \
            goto LABEL;                         \
    } while(0)

void delete_foo(strcut foo *foo)
{
    free(foo->char_field);
    free(foo);
}

Твой вариант:

    struct foo *foo;

    foo = malloc(sizeof(struct foo));

    ENSURE(foo != NULL, foo_alloc_failure_label);

    foo->char_field = malloc(len * sizeof(char));

    ENSURE(foo->char_field != NULL, foo_char_alloc_failure_label);

    result = init_foo(foo, source);

    ENSURE(successful(result), foo_init_failure_label);

    /* do something with foo */
    /* ... */

    delete_foo(foo);
    
foo_char_alloc_failure_label:
    free(foo->char_field);

foo_alloc_failure_label:
    free(foo);

    /* ... */

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

Альтернатива:

    struct foo *foo;

    foo = get_foo(source, len);

    ENSURE(foo != NULL, handle_foo_error_label);
    
    /* do something with foo */
    /* ... */
    
    delete_foo(foo);
    
    /* ... */

Примерный код get_foo::

struct foo *get_foo(FILE *foo_source, size_t len)
{
    struct foo *foo = malloc(sizeof(struct foo));

    ENSURE(foo != NULL, err);

    foo->char_field = malloc(len * sizeof(char));

    ENSURE(foo->char_field != NULL, free_foo);

    /* init foo here */
    /* ... */

    return foo;

free_char_field:
    free(foo->char_field);

free_foo:
    free(foo);

err:
    return NULL;
}
theNamelessOne ★★★★★
()
Ответ на: комментарий от erfea

Помочь то помогут, но недостатков не лишены. Ллучше учиться работать с памятью, это ни фига не сложно

В сложных многопоточных нелинейных (ориентированных на события) случаях (которыми, например, являются все нормальные программы с графическим интерфейсом) — очень даже сложно. И shared_ptr вполне спасает, нужно только циклов не создавать. Можно пользоваться и для массивов, кстати.

note173 ★★★★★
()
Ответ на: комментарий от Kiborg

С долгоживущими понятно что такое не прокатит. Я не понимаю, почему вам это не очевидно.

Это непонятно тебе.

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

Ты сам нарисовал. Только во втором варианте поменяй get_array() на, скажем, array_alloc(), и free() на array_free().

PS: Прогрессирующий склероз, когда кто-то не может запомнить, что функции типа something_alloc() или new_something() возвращают выделенный кусок памяти, который надо освободить вызовом соответствующей something_free()/delete_something()/etc, не лечим.

kemm
()
Ответ на: комментарий от i-rinat

Меня вообще удивляет, что столько проектов упорно не используют его, постоянно имея кучи проблем с текущей памятью.

buddhist ★★★★★
()
Ответ на: комментарий от buddhist

Автор заяляет, что на множестве мелких выделений памяти скорость даже выше, чем при ручном управлении. Видимо за счёт освобождения пачками.

А как оно в реальном использовании? Я о библиотеке только неделю назад узнал.

i-rinat ★★★★★
()
Ответ на: комментарий от schizoid

boost::smart_ptr же.

//
//  smart_ptr.hpp
//
//  For convenience, this header includes the rest of the smart
//  pointer library headers.
//
//  Copyright (c) 2003 Peter Dimov  Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
//  http://www.boost.org/libs/smart_ptr/smart_ptr.htm
//

#include <boost/config.hpp>

#include <boost/scoped_ptr.hpp>
#include <boost/scoped_array.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/shared_array.hpp>

#if !defined(BOOST_NO_MEMBER_TEMPLATES) || defined(BOOST_MSVC6_MEMBER_TEMPLATES)
# include <boost/weak_ptr.hpp>
# include <boost/intrusive_ptr.hpp>
# include <boost/enable_shared_from_this.hpp>
#endif
wota ★★
()
Ответ на: комментарий от quiet_readonly

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

и что? Проблемы будут только если будешь удалять производные от него через указатель на базовый, вроде все или я что-то упустил?

Boy_from_Jungle ★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.