LINUX.ORG.RU

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

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


0

3

Привет всем.

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

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

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

не потерять возвращаемый функцией указатель

anonymous
()

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

$ cat main.c 
#include <stdio.h>
typedef struct type { int a; int b; } type;
type* foo()
{
  type *foo_ptr = (type*)malloc(100000);
  return foo_ptr;
}
int main(int argc, char* argv[])
{
  int *ptr = foo();
  free(ptr);
  return 0;
}
$ gcc main.c 
main.c: In function ‘foo’:
main.c:5: warning: incompatible implicit declaration of built-in function ‘malloc’
main.c: In function ‘main’:
main.c:10: warning: initialization from incompatible pointer type
main.c:11: warning: incompatible implicit declaration of built-in function ‘free’

$ valgrind ./a.out
==2807== Memcheck, a memory error detector
==2807== Copyright (C) 2002-2010, and GNU GPL'd, by Julian Seward et al.
==2807== Using Valgrind-3.6.0.SVN-Debian and LibVEX; rerun with -h for copyright info
==2807== Command: ./a.out
==2807== 
==2807== 
==2807== HEAP SUMMARY:
==2807==     in use at exit: 0 bytes in 0 blocks
==2807==   total heap usage: 1 allocs, 1 frees, 100,000 bytes allocated
==2807== 
==2807== All heap blocks were freed -- no leaks are possible
==2807== 
==2807== For counts of detected and suppressed errors, rerun with: -v
==2807== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 4 from 4)

anonymous
()

Ведь насколько я понимаю в стеке окажутся и затем вытолкнутся всего 4 байта (в случае х86), а сама выделенная память может быть много больше по размеру.

В стеке выделится место под указатель. При выходе из функции стек свернётся назад (место занятое указателем будет освобождено). А вот область выделенная malloc() будет находиться в куче и не освободится пока ты явно этого не сделаешь.

Как правильно проектировать подобный код?

http://ru.wikipedia.org/wiki/Динамически_распределяемая_память

erfea ★★★★★
()

В общем-то malloc выделяет места чуть больше, чем запрошено, и на сдаче хранит размер выделенной области памяти. Поэтому почистит независимо от приведений типов.

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

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

В стеке выделится место под указатель. При выходе из функции стек свернётся назад (место занятое указателем будет освобождено). А вот область выделенная malloc() будет находиться в куче и не освободится пока ты явно этого не сделаешь.

В этом-то и загвоздка. Утечки памяти же неизбежны.

LongLiveUbuntu ★★★★★
() автор топика

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

Совершенно верно.

Как правильно проектировать подобный код?

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

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

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

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

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

Указатель я получаю внутри функции. Или нет?

LongLiveUbuntu ★★★★★
() автор топика

Может лечь поспать? Наутро глядишь такие вопросы и сами отпадут.

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

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

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

Помогут ли обертки такие как «умные указатели»?

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

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

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

Мы блин о сях говорим, не нравится юзай ЯП с GC...

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

И все равно это опасный код

«Си — инструмент, острый, как бритва: с его помощью можно создать и элегантную программу, и кровавое месиво». Лучше научиться пользоваться бритвой 1 раз, чем каждый раз искать для неё подходящий чехол.

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

Все, что я могу, это попытаться обратиться к Qt, скажем.

Qt не решает вопросов с кучей. Там тоже приходится использовать оператор delete и метод QObject::deleteLater(). Это особенность ЯП, нравится тебе или нет.

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

В C++ используют RAII, new/delete _только_ в контрукторах/деструкторах, стандартные контейнеры ну и иногда умные указатели. Использование new/delete/malloc/free/etc в иных местах считается дурным тоном.

Reset ★★★★★
()

будет ли после вызова этой функции возвращена память системе?

нет. надо явно вызвать free для адреса, на который указывает foo_ptr.

Ведь насколько я понимаю в стеке окажутся и затем вытолкнутся всего 4 байта (в случае х86),

совершенно верно. и эти 4ре байта - адрес возврата для ret, но совсем не foo_ptr, который как правило в аккумуляторе. вот если бы ты структуры (или классы в спп) возвращал по значению, тогда да, но в том случае вызываемая сторона для неё место выделила бы перед собственным вызовом на стеке. а простые типы возвращаются в аккумуляторе.

Как правильно проектировать подобный код?

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

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

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

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

в стеке будет только адрес возврата. указатель будет в аккумуляторе

Когда я изучал си, в учебных материалах было черным по белому написано, что при вызове функции выставляется адрес возврата и выделяется на стеке память для объектов передаваемых в функцию и определяемых в ней. Ни слова про указатели там не было (следовательно подчиняется общим правилам) и про «аккумулятор» тоже. Буду благодарен, если тут будет пруф на САБЖ.

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

да ты прав, я забыл, что foo_ptr локальная переменная. мне спать пора. но её всё равно в аккумулятор суют перед возвратом.

int *foo()
{
	int *t = 0;
	return t;
}
gcc -c foo.c && objdump -D foo.o
0000000000000000 <foo>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   48 c7 45 f8 00 00 00    movq   $0x0,-0x8(%rbp)
   b:   00 
   c:   48 8b 45 f8             mov    -0x8(%rbp),%rax
  10:   c9                      leaveq 
  11:   c3                      retq   
обрати внимание на c: 488b45f8

п.с. да, я знаю, что у меня amd64, а не х86

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

да, a, ax, eax, rax, в порядке возростания разрядности. а я вот думаю, что ты аккумулятор в кавычки всё берёшь. вон она как...

п.с. неужели я такой старый и в книжках уже так не пишут?

nanoolinux ★★★★
()

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

Поэтому лучше подобный код делать в таком виде (когда это возможно):

...
int * data = malloc(INT_COUNT * sizeof(int));
foo(data); //foo что-то с этим массивом делает
free(data);
...

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

да, a, ax, eax, rax, в порядке возростания разрядности. а я вот думаю, что ты аккумулятор в кавычки всё берёшь. вон она как...

п.с. неужели я такой старый и в книжках уже так не пишут?

Это видать я тугодум... не понял тебя ;) ЗЫ ЕМНИП компилятор может вообще объекты сразу размещать в регистрах. Я просто в разговоре с ТСом сабж отбросил как лишний ньюанс, сути то оно не меняет.

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

объекты сразу размещать в регистрах

для х86 о таком не слышал. для других архитектур может и бывает, не знаю. но вообще да, это уже оффтоп.

nanoolinux ★★★★
()

Вопрос: будет ли после вызова этой функции возвращена память системе?

нет. Так и возникают утечки памяти.

Как правильно проектировать подобный код?

Не писать на C.

И традиционно: enjoy your C.

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

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

slapin ★★★★★
()

Как правильно проектировать подобный код?

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

А есть ещё либы для автоматического garbage collection. Посмотри boehm gc (сам я ни разу им не пользовался).

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

Не писать на C

Неправильный совет. Правильный — быть внимательным.

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

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

Мы блин о сях говорим, не нравится юзай ЯП с GC...

ну на C++ можно ведь и GC запилить. Если очень надо.

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

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

Какой бессмысленный маразм. Чему поможет выделение памяти вне функции?

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

ога, кто-то любит на языке решать задачи, а кто-то танцевать гопак :)

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

1. Управление будет в одном месте. Меньше вероятность забыть освободить память, меньше дополнительной контекстной нагрузки на функцию.

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

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

Не должны, если сама функция, вызывающая strdup, работает как тот же malloc.

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