Re: recursion and global variables
John wrote:
{ the part about putting the breakpoint and using F5 versus F11 is
off-topic in c.l.c++.m, but the message is x-posted to a Visual
C++ newsgroup, so in c.l.c++.m we'll pretend we don't see F5/F11
comments. thanks. -mod }
It would also help if the poster converted his code into C++
before posting here. I have to guess about a certain number of
things, because the code isn't standard C++. Things like TCHAR
(which I suppose is a typedef for char, but why anyone would use
a typedef for char is beyond me), and _tmain (which I have
treated as if it were simply main).
I have a question about recursion and global variables that may change
in the recursion. The following code is a simple extension of the
DeskCalculator example in chapter 6 of Stroustroup's "C++ Programming
Language" 3rd Edition. I added support for (what I call) intrinsic
functions from math.h using an std::map of function pointers.
To reproduce to problem enter (including return characters):
x
sin(x)
The problem I encountered is that on the return to the function prim()
during the recursion the value of string_value changes from 'sin' to
'x'. The solution, which I have included in the code below, solves the
problem with a static variable to cache the value of the function key
value.
The question I have is why, in the recursed call to:
double iv = (*intr_func[intrs_func])(expr(true))
does string_value contain the value 'x' and not 'sin' which was the
value at the start of the recursion.
Because at some point in the recursion, you changed it.
[...]
tstring string_value;
Note that you have a variable at namespace scope, thus, of
static duration. That means one, and only one instance of the
variable in the entire program.
[..]
Token_value get_token()
{
TCHAR ch = 0;
This is critical. What is TCHAR? Because if it is a signed
type (including char, in many implementations) there is
undefined behavior in the following code. (IMHO, the old C
idiom of using int here is still what works best, e.g.:
int ch = cin.get() ;
while ( isspace( ch ) ) {
ch = cin.get() ;
}
// ...
..)
do { // skip whitespace except '\n'
if( !cin.get(ch) ) return curr_tok = END;
} while( ch!='\n' && isspace(ch) );
And here's the first instance of undefined behavior. In modern
C++, I'd pick up the ctype facet at the start of the function,
with something like:
typedef std::ctype< char >
CType ;
CType const& ctype = std::use_facet< CType
( std::locale() ) ;
and then write this loop :
int ch = cin.get() ;
while ( ch != EOF && ch != '\n' && ctype.is( CType::space, ch ) )
{
ch = cin.get() ;
}
Followed by the switch. But if you're inputting to the int,
something like:
while ( ch != EOF && ch != '\n' && isspace( ch ) ) {
ch = cin.get() ;
}
is also fine (even if it looks a bit C'ish).
switch( ch )
{
[...]
default: // NAME, NAME=, or error
And more undefined behavior. (There was also some in the code
I've cut). You can't just call any of the functions in <cctype>
with a char without running the risk of undefined behavior. You
have, in fact, three choices:
-- use the form of istream::get() (and istream::peek()!) which
returns an int, and handle single characters in an int, in
the grand tradition of C,
-- always check for EOF separately, and cast to unsigned char
before calling any of the functions in <cctype>, or
-- forget about <cctype>, and use the ctype facet in <locale>
(also checking for EOF separately).
The first has the advantage of simplicity, but doesn't allow
easily switching the locale depending on the file (especially in
a multi-threaded environment)---only the last allows this.
if( isalpha(ch) )
{
string_value = ch;
And this is the answer to your initial question: you just
modified the global variable. Since this function is called
right and left in the recursive descent, you never really know
what value string_value might hold. The intent is, I'm sure, to
use it as an additional return value, and to copy it into a
local variable immediately after calling get_token, if it is
relevant.
(FWIW: in production code, I'd have get_token returning some
sort of a class type, with all of the values types somehow
embedded in it. I'd also ensure that the input could come from
an arbitrary file, and not just cin, and that I used the locale
of that file for handling the characters. Presumably, if
Stroustrup didn't do this, it's because this example occurs
relatively early in his presentation, and he hasn't yet
presented all of the tools necessary: the memory management
issues surrounding a dynamically typed Token class are
non-trivial unless you're using the Boehm collector, and locales
are definitly something that would normally only be addressed at
the end of a course. And of course, in production code, no one
writes this sort of program anyway, given the number of tools
available to generate it automatically.)
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orientie objet/
Beratung in objektorientierter Datenverarbeitung
9 place Simard, 78210 St.-Cyr-l'Icole, France, +33 (0)1 30 23 00 34
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]