| Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
| Форум программистов > C/C++: Общие вопросы > Возврат строки неизвестного размера из функции |
| Автор: ama_kid 6.6.2008, 10:13 | ||||
| Доброго всем времени суток... Сразу скажу, что при написании топика почитал http://forum.vingrad.ru/index.php?showtopic=181096 и http://forum.vingrad.ru/index.php?showtopic=191122 тему, выданные мне форумом как "похожие темы", ответы там не сильно помогли, поиском тоже пользовался - не айс... между тем вопрос по большому счету не стОит и того количества букв, которые я тут напишу, но тем не менее, постараюсь в красках и примерах на пальцах объяснить суть ментального геморроя... Итак, недавно пришлось переводить кое-какой код с Дельфи на С++ и столкнулся с интересной для себя теоретической проблемой, которая подспудно грызла меня давно, но как-то не было случая с ней основательно разобраться... Дело в том, что есть, допустим, гипотетический код на дельфи:
1) Очевидно, что если я оставлю функцию в таком виде - я гарантирую себе весёлую жизнь с утечками памяти, ибо delete\free нигде не вызываются и память по выходу из функции утекает. В итоге - закономерный вопрос: где необходимо освобождать память? Выделять память до вызова функции нет смысла, ибо размер буфера неизвестен; освобождать внутри функции - бессмысленно; выделять внутри, а освобождать после выхода - некрасиво (и есть небезосновательное подозрение, что неправильно)... 2) Как вообще правильно действовать в таких случаях? Использование разнообразных классов типа std::string не предлагать, сам знаю, что они для этого и предназначены, но так же помню, что разнообразные библиотечные функции, работающие с char * - работают вполне корректно и возвращают указатели на вполне валидные буферы. Да, я понимаю, что там используется несколько другой подход - передаются указатели на буферы, где необходимо разместить ответ, но не до конца понимаю, где происходит выделение памяти под эти буферы? (Тот же http://msdn.microsoft.com/en-us/library/kk6xf663.aspx - как и где выделяет память под char *strDestination?). Единственный более-менее внятный ответ на http://forum.vingrad.ru/index.php?showtopic=16429 от _hunter'а я увидел у http://forum.vingrad.ru/index.php?showtopic=16429&view=findpost&p=109698, но и там возник вопрос: а что если память под параметр char* param выделена не через new, а допустим, через malloc (при условии, что я не знаю точно)? В общем, буду признателен, если помимо ответов "ха-ха, ты далпайоп!" и "кури маны, дятел!" (как любит говорить мне мой друг) будут более содержательные разъяснения |
| Автор: Fazil6 6.6.2008, 10:37 | ||
ну если посмотреть апишные функции, то как правило им передаётся буфер и размер, а они возвращают количество записанных символов и, если что, количество символов, которые не влезли. По большому счету мне непонятны твои сомнения относительно new или malloc. Ведь полюбому буфер надо выделять перед вызовом и функции побарабану как он получен (хоть массив в стеке). Возвращать из функции имеет смысл если возвращается const char* заранее определененый, а в твоем случае нужно подготовленный буфер передавать и заполнять его. Добавлено через 5 минут и 2 секунды если смотреть твой код
неправильно это. Плохо делать выделение памяти внутри функции. Так ты делаешь вызывающий код зависимым от реализации этой функции. |
| Автор: Walker 6.6.2008, 10:45 | ||
| Хоть сам ещё нахожусь в процессе познания, принять участие в обсуждении интересно. Может вместе найдём истину. Сразу оговорюсь, я работаю исключительно с С. На первый взгляд складывается ощущение, что при грамотном проектировании такой ситуации возникать просто не должно.
Правильно понимаете. Это чревато следующей ошибкой, которая ловится, зачастую только под отладчиком. Если Вы передаёте адрес как аргумент, то работаете с локальной копией указателя, и внешний мир ничего не знает о выделенном участке. Это будет бесцельная трата памяти. Если же вы используете адрес в качестве возвращаемого значения, то аргументом передавайте размер, Ваша функция будет выполнять роль оболочки над malloc. Напишите обратную функцию - оболочку над free. Библиотечные функции и strcpy в том числе используют именно преопределённые буферы, за выделение и освобождение которых отвечаете Вы. Попробуйте представить иной пример, тогда найдём в открытых исходниках аналог и разберёмся. Пока я такого не встречал. PS Пока инет глючил, Fazil6 опередил. |
| Автор: Fazil6 6.6.2008, 10:51 |
| как вариант сначала запрашивать размер буфера, а потом выделять буфер и вызывать GetString, но честно говоря я бы так не делал. По любому используя С++ лучше контейнер (стандартный или самодельный) вместо char* |
| Автор: Fazil6 6.6.2008, 10:56 | ||
вот те раз... приехали... нигде он ее не выделяет. Оба буфера выделены до вызова. |
| Автор: Mayk 6.6.2008, 10:56 | ||
realloc курить сюда. зы! опять мои посты не склеились >ОДНАКО< |
| Автор: Andrey44 6.6.2008, 11:26 |
| А можно ли вообще возвращать адрес локальной переменной? Компилятор по-этому поводу предупреждает! |
| Автор: ama_kid 6.6.2008, 11:29 | ||||||||||
Ага, это если память выделена malloc... А если через new? Делать delete и new заново? Насколько это корректно с точки зрения областей видимости? А если неизвестно - через что выделена память?
|
| Автор: MAKCim 6.6.2008, 11:30 | ||
можно, но не нужно Добавлено через 2 минуты и 47 секунд
два варианта 1. отделить функционал от работы с памятью (выделение/освобождение) 2. использовать статический буфер (TLS-буфер в случае многопоточности) |
| Автор: Alek86 6.6.2008, 11:35 | ||
на плюсах это легко:
а не ООП тем и хуже, что ты сам обязан за всем следить |
| Автор: ama_kid 6.6.2008, 11:41 | ||
Да я-то и не против следить, мне собственно и нужно знать - КАК следить? Как правильно будет написать возврат строки из функции?
|
| Автор: Mayk 6.6.2008, 11:45 |
См аллокаторы в stl. |
| Автор: mes 6.6.2008, 11:53 | ||||
| и в С и в С++ работа по выделению памяти внутри функции не этична но в С++ можно написать оболочку над буффером (чем с данной точки зрения и является std::string) "стык" между С и С++ приводится к сишнему виду, со всеми вытекающими проблемами.. поэтому имхо вопрос должен относится к чистому С, а не к плюсам
перекосило от конструкции, хотя отработает она без проблем. |
| Автор: Alek86 6.6.2008, 11:55 | ||
думаю имелось в виду сделать 2 функции - получение длины данных и заполнение буфера тогда - получил длину - создал буфер - заполнил (GetString) - воспользовался данными - удалил буфер а если получение длины неотделимо от GetString, то или выделяй большой буфер и пускай функция выдает ошибку, если его оказалось недостаточно или уж выделяй внутри функции и возвращай
Добавлено @ 11:59 стандартный прием когда хочется, чтобы функция нужные данные возвращала, и хочется избежать лишних вызовов конструкторов копирования если функция внутри dll находится, то плюсы тут наравне с сями, ибо реализация auto_ptr не специализирована и даже размеры классов для борланда и мелкософтового компилеров могут отличаться |
| Автор: ama_kid 6.6.2008, 11:59 |
| Опять же - поднимаемся до уровня библиотеки шаблонов С++. Оно, конечно, введено в стандарт и все такое, но проблема в том, что во многих компиляторах С, применяемых, допустим, в промышленных контроллерах, она попросту отсутствует. Поэтому интересно узнать решение средствами языка, а не каких-то библиотек (даже если это и трудоёмко). Или наоборот - получить подтверждение, что это невозможно... Добавлено через 2 минуты и 32 секунды Во-во, про второй способ поподробнее пожалуйста... На что должен указывать char* p_str? |
| Автор: Alek86 6.6.2008, 12:03 |
на начало строки... |
| Автор: Mayk 6.6.2008, 12:05 | ||
delete [] != delete.
Ну так вместо шаблонов передавай указатели на malloc/realloc/free если так заботишся о обобщенности, делов то. |
| Автор: ama_kid 6.6.2008, 12:07 |
| А память под строку выделена\освобождена где? |
| Автор: Mayk 6.6.2008, 12:09 |
ДО вызова/а если требутеся - то ВО время. |
| Автор: Lazin 6.6.2008, 12:11 | ||||||
можно передавать в функцию не только массив и его размер, но и указатель на функцию изменяющую размер массива...
использовать можно как с динамическим массивом
так и с массивом размещенным в стеке
|
| Автор: Alek86 6.6.2008, 12:11 |
точно интересно, какой хороший человек решил их разделять... внутри функции но это только в том случае, если никак не можешь отделить подсчет необходимой длины от GetString (или он просто будет долго работать) |
| Автор: ama_kid 6.6.2008, 12:12 | ||
|
| Автор: Mayk 6.6.2008, 12:14 |
std::copy блин На что передаются указатели в аллокаторы? На что передаются указатели в аллокаторы? На что передаются указатели в аллокаторы? На что передаются указатели в аллокаторы? На что передаются указатели в аллокаторы? На что передаются указатели в аллокаторы? На что передаются указатели в аллокаторы? |
| Автор: ama_kid 6.6.2008, 12:14 |
| Lazin, привел хороший пример, но к тебе тот же вопрос... |
| Автор: Lazin 6.6.2008, 12:15 |
новый массив = new[] memcpy из старого в новый (если нужно) delete[] старый массив |
| Автор: mes 6.6.2008, 12:16 |
тут два варианта , или "чистый" виртуальный интерфейс, либо С -подход (не смотря на то что прога на С++) |
| Автор: Lazin 6.6.2008, 12:16 |
| в си алокаторов и new/delete нету, так к слову... |
| Автор: nirburg 6.6.2008, 12:17 | ||
| ama_kid, перед вызовом подобных функций использую предварительное выделение заведомо достаточного объема памяти в стеке, либо хипе (зависит от необходимых размеров). соответственно, память освобождается в той же функции, которая ее выделяла. если максимальный размер возвращаемых данных никак заранее нельзя определить (хотя бы примерно), то, дабы не обременять себя "ментальным геморроем", использую std::string. т.к., судя по всему, эти пути решения вас не устраивают, можно делать "пробный" вызов функции для определения необходимого объема памяти, выделять эту память, после чего вызывать функцию "по-настоящему". такой вариант, кажется, применяется в некоторых функциях ядра Windows, или, по крайней мере, где-то недалеко от ядра поясню:
как-то так, в общем. |
| Автор: ama_kid 6.6.2008, 12:17 | ||
| хмм.. ну тада уж проще использовать с std::string Добавлено @ 12:20
Ну, на самом деле иногда приходится работать со срещенным С\С++ - new\delete есть, а библиотеки шаблонов нет Добавлено @ 12:21 В общем-то полностью согласен, и изначальный вопрос родился из мыслей: "а нельзя ли сделать как-нить эдак, чтобы не так?!" |
| Автор: Alek86 6.6.2008, 12:24 |
даже чистый вирт интерфейс не может работать с переменными типов std::auto_ptr и т.п. |
| Автор: mes 6.6.2008, 12:30 | ||
естественно .. однако это не мешает использовать нам их в реализации .. |
| Автор: Lazin 6.6.2008, 12:31 | ||
все будет ОК, что-бы удалить блок памяти, не нужно знать его размер |
| Автор: nirburg 6.6.2008, 12:32 |
как-нибудь эдак, конечно, можно. вопрос - нужно ли? (шепотом) там народ уже интерфейсы обсуждает )) скоро, чувствую, все сведется к необходимости создания дерева классов со сложной иерархией, наследованием, и прочей модной шелухой =) |
| Автор: mes 6.6.2008, 12:32 | ||
| тем более вопрос был не об auto_ptr , а о том как избежать проблем с выделением памяти внутри функции Добавлено через 1 минуту и 11 секунд
)) |
| Автор: ama_kid 6.6.2008, 12:35 |
| Удалить-то мы его удалим, а куда денется выделенная по-новой память при выходе из функции? |
| Автор: mes 6.6.2008, 12:39 | ||
если изменять память внутри функции надо передаватж в нее ссылку на указатель.. тогда ее можно будет удалить извне .. но в таком случае саму функцию желательно называть типа newString - чтобы было ясно что внутри происходит выделение памяти .. а так же желательно чтоб был ее антоним типа deleteString - но такой подход только в особенных случаях в остальных как и говорилось выше - нужно выделять памятж вне функции |
| Автор: ama_kid 6.6.2008, 12:51 | ||
| Опять же - а если вне функции неизвестен размер требуемой памяти? Хорошо, у меня вроде бы сформировался более-менее основной вопрос, попробую его сформулировать: как после выполнения оператора
1) размер строки становится известен только внутри GetString()... 2) пользуясь new\delete? P.S. Для случаев с malloc\realloc\free я более-менее уже понимаю... |
| Автор: vinter 6.6.2008, 13:01 |
| присваивать так низя, юзай strcpy |
| Автор: ama_kid 6.6.2008, 13:06 |
| что передавать в strcpy? Ну это гипотетический пример, хоть strcpy, хоть что-то другое , без разницы, главное - на выходе получить валидный буфер |
| Автор: Fazil6 6.6.2008, 13:09 | ||||||
Добавлено через 1 минуту и 8 секунд кстати
|
| Автор: ama_kid 6.6.2008, 13:19 |
| Fazil6, ага, вариант понятен... В принципе, я так и делал всегда... Кстати, щас только сообразил, что непроходимо туплю - сам же на свой вопрос вывесил в топике ответ - ссылка ответа от http://forum.vingrad.ru/index.php?showtopic=16429&view=findpost&p=109698. В связи с этим вопрос - насколько корректен приведённый там пример? Просто никто там после этого не отписался по поводу правильности\неправильности. Если он правилен - тогда вопрос снимается... |
| Автор: JackYF 6.6.2008, 13:25 | ||||
| Ого, сколько написали... Подытожу по себе (то есть, как бы это делал я). Варианта 2: 1) целевой язык - С
2) целевой язык - С++
В первом случае ты сам заботишься о том, чтобы освободить выделенную внутри функции память, во втором случае за тебя всё уже сделано. |
| Автор: ama_kid 6.6.2008, 13:28 | ||
|
| Автор: nirburg 6.6.2008, 13:28 |
| JackYF, в C нет new и delete |
| Автор: mes 6.6.2008, 13:32 | ||||
как я понял имеется ввиду этот ответ: http://forum.vingrad.ru/index.php?showtopic=16429&view=findpost&p=109698
|
| Автор: Fazil6 6.6.2008, 13:34 | ||||
Зная этого чела лично, могу сказать с определенностью, что сейчас он так делать не стал бы. Я тоже так делать не сталбы.
|
| Автор: nirburg 6.6.2008, 13:41 | ||
ну вот, приехали. new, оказывается, выделяет память в стеке? или C++ обзавелся хитрым сборщиком мусора, который подтирает всю выделенную в функции память? |
| Автор: mes 6.6.2008, 13:47 | ||
ничем не обзавелся .. просто значения указателя не возврашается из функции - т.е указатель переданный в функцию, после ее завершения, продолжает показывать на ту же самую область что и раньше (которая к тому же была удалена внутри функции) чтоб избежать этого надо либо передавать указатель на указатель, либо указатель по ссылке в коментариях в предыдушем посту это и указано )) |
| Автор: Daevaorn 6.6.2008, 13:59 | ||
Не, ну выделить память внутри функции и выдать её наружу вполне себе юзкейс. Так, допустим, malloc поступает |
| Автор: mes 6.6.2008, 14:04 | ||
нет проблемы в том, чтоб выделить память внутри функции проблема в том чтоб знать когда она выделилась и удалить ее когда нужно. и самое главное, чтоб этот процесс не отвлекал от основной цели - программирование, ибо если постояно помнить реализацию каждой функции, то и с ума сойти можно |
| Автор: nirburg 6.6.2008, 14:05 |
| mes, прошу прощения, неправильно понял Daevaorn, ну malloc'y на роду написано память внутри себя выделять. а если так будет поступать каждая гордая функция, то что с программой будет твориться, я не представляю |
| Автор: mes 6.6.2008, 14:07 |
| malloc и new тем и хороши что всегда не задумываясь (о том как реализована функция) понятно, что делает функция и одназначно напрашивается вывод о применение ее антонима. однако, что нужно делать после функции GetString (..) (удалять память или нет) имхо отнюдь не очевидно поэтому (на мой взгляд) и принято "неписанное " соглашении о использовании внутри функции зараннее выделенного буффера.. |
| Автор: Daevaorn 6.6.2008, 14:15 | ||
а если строка больше чем буфер? Тогда ещё одну функцию заводить, которая вернет размер, потом выделить память, и только потом передать в функцию - много букв Хотя, конечно, это оптимально с точки зрения прозрачности кода. |
| Автор: JackYF 6.6.2008, 14:33 | ||
|
| Автор: mes 6.6.2008, 15:01 | ||||
вот пример без добавления второй функции
|
| Автор: Daevaorn 6.6.2008, 15:09 |
ну по сути вы её добавили, сделав в GetString проверку `if( p == 0 )`. |
| Автор: ama_kid 6.6.2008, 15:26 |
| В общем, как я понял, сделать средствами new\delete (без привлечения библиотек-контейнеров и выкрутасов с несколькими функциями\вызовами) аналогичный дельфёвому фокус не удастся. Что ж, будет еще один плюс в пользу malloc\realloc Хотя, если появятся новые идеи - буду рад увидеть, да и для потомков, думаю, будет полезно... |