Рекурсия And Стек
Я "боролся" с этим подсчетом подвызовов. Экспериментально замерял сколько подвызовов данная функция "держит", принимал это за максисмум, а дальше если число превышается, то тупо останавливал выполнение.
Я понимаю, что проблема тривиальная... Но поиском ответа на вопрос не нашел. Подскажите как бороться с переполнением стека.
Я "боролся" с этим подсчетом подвызовов. Экспериментально замерял сколько подвызовов данная функция "держит", принимал это за максисмум, а дальше если число превышается, то тупо останавливал выполнение.
{...}
catch(EOverflow &e)
{...}
Или это все уже из области слишком нетривиального?...
А есть какой способ продолжить выполнение, сбросив, допустим, часть стека в оперативу, или вообще может есть какой-нибудь тип функций, работающих без заваливания стека своими возвратами :) ...
Или это все уже из области слишком нетривиального?...
Думаю проще будет сделать функцию нерекурсивной.
{...}
catch(EOverflow &e)
{...}
Не работает, однако... У меня в функции несколько вариантов ее подвызова, дак я даже не общий трай, а отдельно для каждого подвызова сделал так... И по нулям, трай даже не срабатывает... Зато теперь сообщение о переполнении не выскакивает - прога просто сразу аннигилируется :D
Думаю проще будет сделать функцию нерекурсивной.
Думал над этим... Слишком не оптимально получается... Собственно говоря все началось со скачивания мат-парсера, выложенного на этом сайте - он работает с ошибкой. И задался я целью попробовать написать для себя мат-парсер (ну мало в и-нете мат-парсеров :) ). Сперва как раз и написал без рекурсии - работает. Потом пришла в голову неплохая оптимизация с рекурсией.
Дак вот рекурсивный вариант перемалывает выражение, длинной в 60 вордовских листов, не успеваешь кнопку отпустить, а первый вариант молотит это выражение полминуты... Проблема в том, что стоит добавить в то выражение еще хоть один символ, и рекурсивный падает, а первый вариант все равно работает......
Я полон решимости решить проблему со стеком, и при этом хочу сделать это применимым для любого случая!
Пока идея заключается в том, чтобы в случае перепнения сбрасывать определенную часть стека в оперативу.
Задача в том чтобы определить сколько заданная функция запихивает в стек при одиночном вызове.
Подскажите еще пожалуйста, что кидается в стек при вызове функции, и что каждый элемент при этом занимает.
Я полон решимости решить проблему со стеком, и при этом хочу сделать это применимым для любого случая!
Пока идея заключается в том, чтобы в случае перепнения сбрасывать определенную часть стека в оперативу.
Задача в том чтобы определить сколько заданная функция запихивает в стек при одиночном вызове.
Debug Windows->CPU там есть окошко стека
Подскажите еще пожалуйста, что кидается в стек при вызове функции, и что каждый элемент при этом занимает.
Я полон решимости решить проблему со стеком, и при этом хочу сделать это применимым для любого случая!
В стек кидается очень много и всякого разного... :)
Это и параметры вызова, и копии регистров, и локальные переменные, и различные данные для механизмов проверки целостности стека и пр.
Поэтому для любого случая не получится скорее всего. Да и не за чем.
Как уже говорилось, лучше отказаться от рекурсии.
Это выигрышный вариант по нескольким критериям: память, скорость и пр.
И основного, что понял: т.к. в стек кидаются параметры и локальные переменные, то это то самое, что необходимо минимизировать в рекурсивной функции. Не дай боже делать локальные статические массивы - все только динамичкой.
Крайний вариант - собрать вообще все переменные в динамический массив (только нужно жостко следить за его удалением по окончании работы функции).
Меня еще вот какой вопрос терзает: может быть есть како-нибуть тип функций в билдере(да и не только), которые все свои данные(за исключением пожалуй адреса возврата) хранят в памяти, а не в стеке? Такая функция работала бы помедленней(самую малость, т.к. стек у виндоз-приложений все равно виртуальный, и валяется в памяти), но для рекурсии это был бы идеальный вариант.
В общем, если там делать без рекурсии, то придется делать многомерный разноветкоразмерный массив :D (дерево, короче) для хранения промежуточных результатов вычислений. А с рекурсией имеем вполне красивенькую функцию... которая имеет несколько вариаций своего подвызова...
В общем, если там делать без рекурсии, то придется делать многомерный разноветкоразмерный массив :D (дерево, короче) для хранения промежуточных результатов вычислений.
А так тебе придется сделать тоже самое только через... другое место.
Любая рекурсия в конечном счете разворачивается в цикл. Это может сделать программист, а может сделать компилятор. Ты же пытаешься сделать нечто на промежуточном уровне. Вопрос целесообразности.
Я б мог кинуть код... ток боюсь он длинноват... :)
В общем, если там делать без рекурсии, то придется делать многомерный разноветкоразмерный массив :D (дерево, короче) для хранения промежуточных результатов вычислений. А с рекурсией имеем вполне красивенькую функцию... которая имеет несколько вариаций своего подвызова...
Ну показал бы... глядишь и мысли бы появились, либо как избавиться от нее, либо как ловчее ее использовать
Ну показал бы... глядишь и мысли бы появились, либо как избавиться от нее, либо как ловчее ее использовать
В краце (сложно про нее сказать в краце):
В первой половине выполнения (когда идет нарастание числа подвызовов, идем в глубину) рекурс как бы строит дерево очередности выполнения с запоминанием границ выражений (на каждом следующем этапе делит выражения на более мелкие части). Когда доходит до конца (делить уже нечего и это "дерево" построено) функции начинают возвращать . Т.е. начинается "спуск по ветвям к корню". У каждого узла получается всегда либо одна ветвь (когда считать уже нечего, нужно просто воспользоваться функцией TryStrToFloat для перевода строчки в число) либо две ветви (два значения (уже не строковых) и операция между ними).
Исходник немножко оформлю и выложу скорее всего завтра...
С вас предложения :)
//возвращающая результат заданного выражения
double op(char *str,int strLen)
{
int i,l[2],opId=-1,opPos=-1;
char *op_str[2], oper=0, tmp;
double res,res1,res2;
//Ищем оператор в строке(справа-налево) в порядке обратном
//приоритета операторов, пропуская содержимое скобок
if(!FindOperatorPriorReverse(str,strLen-1,0,&opPos,&opId)){
//Если при этом есть скобки(дополнительная глубина), то раскрываем их
if(str[strLen-1]==')'){
if(str[0]=='('){
op_str[0]=str+1;
l[0]=strLen-2;
res = op(op_str[0],l[0]);//Выполняем op для выражения в скобках
}
else if(str[0]=='-' && str[1]=='('){
op_str[0]=str+2;
l[0]=strLen-3;
res = -op(op_str[0],l[0]);//Для скобок с инвертирующим минусом перед ними
};
}
//Если скобок нет, то строка - это число в текстовом
//виде, которое и нужно просто перевести в числовой
//вид и возвратить
else{
tmp = str[strLen];
str[strLen]=0;
if(!TryStrToFloat((String)str,res)){
if(str[0]=='(')MessageBox(0,"Лишняя открывающая скобка!","Ошибка!",MB_ICONEXCLAMATION|MB_OK);
else MessageBox(0,"Недопустимый аргумент!","Ошибка!",MB_ICONEXCLAMATION|MB_OK);
res = NaN;
};
str[strLen]=tmp;
};
}
//Если в строке присутствует операция...
else{
oper = str[opPos];
tmp = str[strLen];
str[opPos] = 0;
str[strLen]= 0;
//Первый операнд
op_str[0] = str;
l[0] = opPos;
res1 = op(op_str[0],l[0]);
//Второй операнд
op_str[1] = str+opPos+1;
l[1] = strLen-opPos-1;
res2 = op(op_str[1],l[1]);
switch(oper){
case '^': res = pow(res1,res2);break;
case '/': res = res1/res2;break;
case '*': res = res1*res2;break;
case '-': res = res1-res2;break;
case '+': res = res1+res2;break;
};
//Восстанавливаем обнуления
str[opPos]=oper;
str[strLen]=tmp;
};
return res;
}
Функция bool FindOperatorPriorReverse(char *str, int Begin, int End, int *Pos, int *Id), возвращает true, если операция в строке найдена, и false в противном случае; имеет следующие параметры:
str = указатель на строчку в которой искать
Begin = начальная позиция для поиска
End = конечная позиция (End<Begin, ищем с конца)
Pos = указатель на переменную, в которую вернется позиция найденной операции
Id = указатель на переменную, в которую вернется ИД операции (операции для поиска согласно приоритета "^/*-+")
Ее код не выкладывал для укорочения выложенного, т.к. сама она еще вызывает две функции...
Шо, не кто не подсобит с рекурсией? Т.е. о том как ее убрать или хотя б ловчей использовать.
первая - разбивка выражения на токены;
вторая - проход по списку токенов.
Подробнее остановлюсь на второй подзадаче.
Хоть решение и не очень красивое (позднее объясню, в чем некрасивость), но его можно потом сделать красивее.
В общем случае у нас идет чередование: цисло - операция - число, иногда встречаются скобки.
Идем по списку токенов:
1) первое число помещаем в стек;
2) помещаем туда же операцию
3) помещаем следующее число;
4) смотрим следующую операцию, если её приоретет не выше приоретета предыдущей операции, раскручиваем стек; если же выше, то выполняем шаг 2);
5) и так повторяем до конца выражения, в конце раскручиваем стек.
Пример:
2+4*6+5
Состояние стека:
+ + + +
4 4 24 5
*
6
Для обработка скобок вводим доп.шаги:
3а) если встретилась открывающая скобка, то заносим её в стек и начинаем процедуру с шага 1);
3б) если встретилась закрывающая скобка, то раскручиваем стек до первой открывающей скобки.
Пример:
2+4*(2+4)+5
Состояние стека:
+ + + + + + +
4 4 4 4 4 24 5
* * * *
( ( ( 6
2 2
+
4
Некрасиво то, что стек у нас хранит как числа, так и операции, т.е. два разных типа, которые можно объединить только одним понятием - токен.
Пример:
2+4*6+5
Состояние стека:
+ + + +
4 4 24 5
*
6
Идея мне очень нравится :), правда тут придется работать в режиме асма...
И опять же не куда не девается работа со СТЕКОМ, который будет лимитировать все своим крохотным размером...
Кстате не подскажете как с флоатами в асме работать? Я так помню там отдельный стек, куда все пихать, и с двумя последними можно работать, вроде так? И подскажите функции, или крохотный примерчик :) , хоть это уже и не по теме...
Идея мне очень нравится :), правда тут придется работать в режиме асма...
И опять же не куда не девается работа со СТЕКОМ, который будет лимитировать все своим крохотным размером...
Не вижу необходимости асма.
Под стеком я понимал не программный стек, а просто структуру хранения данных, например обычный стандартный контейнер.
Не вижу необходимости асма.
Под стеком я понимал не программный стек, а просто структуру хранения данных, например обычный стандартный контейнер.
стандартный контейнер - что это? я не в курсе...
Кстати я поразмышлял, в том коде будет сложно реализовать скобки. Т.к. скобки нарушают стандартную комбинацию операнд-операция-операнд. Придется под скобку резервированть некоторое число, и при написании этого числа юзером (случайно взял и попал) получим бяку...
(правда можно каждый токен явно описывать как
double Value;
bool IsOperand; //Истинно - значит операнд, иначе - это операция
};
Но это - ...гхм...)
Кстати структурка, описывающая лексическую единицу:
{
int who;
double ch;
};
Где who - это и число, и скобка, и функция и переменная, в общем все что угодно. А ch инициализируется если who - число.
Единственное - ни пойму - где ты достал 60 листов формул. Подскажи! Я бы свой калькулетр оттестировал. Правда у меня другая беда была. Надо было мне мою функцию из DLL в цикле вызывать. Так вот: при статическом подключении происходит страшная утечка памяти. После n-й итерации виртуальная память системы просто заканчивается и программа вылетает. Пришлось загружать DLL динамически и освобождать ее же через некоторое кол-во итераций. Не подскажите-ли как с этим можно бороться?
Так вот: при статическом подключении происходит страшная утечка памяти. После n-й итерации виртуальная память системы просто заканчивается и программа вылетает. Пришлось загружать DLL динамически и освобождать ее же через некоторое кол-во итераций. Не подскажите-ли как с этим можно бороться?
Проверь свой код на утечки памяти... :)
стандартный контейнер - что это? я не в курсе...
std::vector
std::list
std::map
std::deque
и пр.
Единственное - ни пойму - где ты достал 60 листов формул. Подскажи!
Да фонарно :) Пишешь небольшое выражение, так чтоб содержало всевозможные вариации-перевариации всего что только можно, ставишь в конце выражения "+", копируешь все это хозяйство, и вставляешь до посинения :D
Кстати, если кто занимался такой охинеей, как написанием подобного строчного калькулятора, киньте плз исходники (хотяб ключевых функций), или сцилку, если таковая есть. И если можно комментарий от автора...
ЗЫ: Доклад в универе по данной теме заработал... Нужны примеры... много :)
std::vector
std::list
std::map
std::deque
и пр.
Благодарю!
Правда, по опыту работы с std::vector, думаю, что сильно пострадает скорость (по сравнению с программным стеком)... Хотя, наверное, это и есть цена возможности работать с практически не ограниченными по размеру выражениями.
Кстати, если кто занимался такой охинеей, как написанием подобного строчного калькулятора, киньте плз исходники (хотяб ключевых функций), или сцилку, если таковая есть. И если можно комментарий от автора...
ЗЫ: Доклад в универе по данной теме заработал... Нужны примеры... много :)
Файл с кодом прилагаю.
Извини, что несколько длинновато, зато готовая функция. Правда в таком виде она тебе не подойдет, т.к. работает с огранниченным кол-вом лексемм.
В краце об аргументах:
1) - указатель на строку сод. ф-лу
2) - указатель на массив структур, содержащие имена переменных и их значения, если в твоих ф-лах не используется символьных пер-ных, то NULL
3) - размер вышеописанного массива (в штуках, а не в байтах :-))
4) - код ошибки, если оная произошла.
ret.value - значение, посчитанной ф-лы.
Да, я тут не предусмотрел ошибку переполнения массива лексемм, так, что поосторожнее с этим.
В ПРОМЕЖУТОЧНЫХ функциях - думаю разберешся.
Есть предположение, что данный алгоритм хорошо оптимизирован.
Буду признателен, если кто-нибудь опровергнет или подтвердит данное предположене.
Вообще, заранее благодарю за любую критику. Можно на мыло [email]dark_king@inbox.ru[/email]
Кстати, если кто занимался такой охинеей, как написанием подобного строчного калькулятора, киньте плз исходники (хотяб ключевых функций), или сцилку, если таковая есть. И если можно комментарий от автора...
код приложил в виде файла, написал длинное описание, но оно кильнулось при попытке отправить, так что если не разбирешся, спрашивай. За критику проги буду благодарить всех!:-) Кидайте идеи по совершенствованию на ящик [email]dark_king@inbox.ru[/email]
Тебе совершенствовать придется, т.к. ф-я работает с огр. кол-вом лексемм.
код приложил в виде файла, написал длинное описание, но оно кильнулось при попытке отправить, так что если не разбирешся, спрашивай. За критику проги буду благодарить всех!:-) Кидайте идеи по совершенствованию на ящик [email]dark_king@inbox.ru[/email]
Тебе совершенствовать придется, т.к. ф-я работает с огр. кол-вом лексемм.
Как кильнулось?! Совсем?! Если файлик вдруг не совсем умер :) мож на мылье отправишь... Плииииз (jump99 сбк yandex тчк ru)
Код длинный... Пока разберусь - сессия кончится :D
Как кильнулось?! Совсем?! Если файлик вдруг не совсем умер :) мож на мылье отправишь... Плииииз (jump99 сбк yandex тчк ru)
Код длинный... Пока разберусь - сессия кончится :D
В общем писал в ответ, а при попытке отправить произошел сбой связи. Вот так и кильнулось.
Ну да ладно, сейчас повторим.
variable - структура содержащая информацию о символьных переменных (name - имя, znachen - значение), если такие используются.
getprior - внутренняя ф-ция, отвечающая за выдачу приоритета операциям.
digitlen - возвращает длину (в символах) числа, записанного по правилам С++.
double calculator(char*, variable*, int, char*&) - основная ф-ия.
Параметры:
1) IN указатель на строку, содержащую формулу.
2) IN указатель на массив структур, содержащих значения символьных переменных, использующихся в формуле. (NULL, если не используются)
3) IN количество вышеописанных структур (в штуках, а не в байтах)
4) OUT описание ошибки, если таковая имела место.
Тьфу блин, да есть же описание.
Посмотри предыдущий ответ, все-таки дошел. А большого описания - шаг за шагом никогда и не было.
Писать его в лом. Так что задавай вопросы, отвечу.
....
Благодарствую! ...Дак эта штука еще и переменные понимает! Четко :)
Сори, что сразу описание не заметил... обычный затуп с перелистом странички.