Declared variable not "in current context"

Posted by Darwin on Sat 05 Apr 2008 10:41 AM — 31 posts, 125,306 views.

USA #0
I've got an odd problem and I'm not sure what is causing it.

I've created a new skill to tan skins. I have it using add_timer to add a delay, pretty much so that the commands can't be stacked as that would cause the process to stop. Anyway, that part works just fine. When the timer ends and returns to the skill function I have a series of if checks against number_percent to see if the process failed or not.

Pseudo code for those checks looks something like this:
if(number_percent > learned[skill])  // check random number vs learned percent
{
  switch(objvalue) // object state
  {
    case 1: // various "fail" messages
      print "You failed"
      if(number_percent > 85) // chance to ruin/destroy item
        extract_obj(obj)
      return
    case 2:
      print "You failed"
      if(number_percent > 90)
        extract_obj(obj)
      return
    case 3:
      print "You failed"
      if(number_percent > 95)
        extract_obj(obj)
      return
  }
}

Testing this code for several hours, I noticed the skill was failing fairly often, so I raised the values 85, 90 and 95 to 93, 95, 97 respectively. That didn't cause the fail rate to lessen any. This is where the odd part comes in. I added a variable declaration to the top of the function like "int perc = number_percent();" I then replaced all the number_percents in the switch cases with perc so the value wouldn't be different for each case. This didn't change anything. My objects were still getting extracted at an almost 100% rate.

I ran this through gdb to check the value of the variable. I set a break point on the function and went to test it. The break point was reached so I checked the current values of all the variables in the function with "info locals" All of the variables showed up except for the "perc" variable I added. Doing a "list" showed that it was there and declared. I tried "p perc" but gdb just gives the response 'No symbol "perc" in current context.'

That didn't help. I changed the if checks within the switch to read <= 7, 5, 3 and declared perc as "int perc = 10;" Even that didn't change anything. The skill still failed and destroyed the object every time, even though 10 is clearly not less than 7, 5 or 3.

So, why is this variable not showing up in the scope of this function?
Australia #1
I think you would need to show the actual function, to me it sounds more like a logic error where your doing something after the block you have shown that changes everything, but without seeing the code its impossible to tell.

Australia Forum Administrator #2
I agree it would help to see the function. I would also add a default case handler, eg.


case 3:
      print "You failed"
      if(number_percent > 95)
        extract_obj(obj)
      return

default:
      print "Unexpected objvalue";
      return;

  }


Anyway, the code you posted doesn't look like C to me. I don't see how it will compile. Just looking at part of it, this:


case 3:
      print "You failed"
      if(number_percent > 95)
        extract_obj(obj)
      return
  }


Should have 3 more semicolons and some brackets, like this:


case 3:
      print ("You failed");
      if(number_percent > 95)
        extract_obj(obj);
      return;
  }


Oh, I see, you said pseudo-code. Well again, it will help to see the actual code.
USA #3
Well, it turns out I did have an error. They were semicolons on the ends of my if lines. Anyway, that still didn't fix the problem as the variable wasn't showing up as in the context of the function.

I'm not sure how it could be a logic problem. The variable, no matter what it was, was not showing up, according to gdb, as being in the context of the function. I tried declaring it with no value, then declaring it with a value. In both cases the variable's value could not be checked as it wasn't showing up in the function context.
Australia Forum Administrator #4
Well, once again, it is hard to debug your logic, or the way you are using gdb, without showing us any of your code.

At least show the function and the place where you are declaring this variable, with a few lines around them. One thing that springs to mind is that it is optimized away. Did you suppress optimization?
USA #5
This is the only part of the function that uses the variable.
void do_tan( CHAR_DATA *ch, char *argument )
{
	OBJ_DATA *skin;
	char buf[MAX_STRING_LENGTH];
	char arg1[MAX_STRING_LENGTH];
        int perc;

	if(IS_NPC(ch))
	{
		send_to_char("&WHuh?\n\r", ch);
		return;
	}

	if ( ch->pcdata->learned[gsn_tan] == 0 )
	{
		send_to_char( "You don't know how to do that... yet.\n\r", ch);
		return;
	}
...
	if ( (number_percent() > UMAX(ch->pcdata->learned[gsn_tan], 20)  ) && !IS_IMMORTAL(ch))
	{
                perc = 10;
		switch(skin->value[0])
		{
			case 1:
				ch_printf(ch, "&WYou fail to use enough salts to cover %s.\n\r&D", skin->short_descr);
				if(perc <= 7)
				{
					ch_printf(ch, "&RThe lack of salt has destroyed your %s.&D\n\r", myobj(skin->short_descr));
					extract_obj(skin);
				}
				break;
			case 2:
				ch_printf(ch, "&WYou miss large sections of the skin while removing the flesh and fat.\n\r&D");
				if(perc <= 5)
				{
					ch_printf(ch, "&R%s has begun to decay.&D\n\r", capitalize(skin->short_descr));
					extract_obj(skin);
				}
				break;
			case 3:
				ch_printf(ch, "&WYou clumsily spill your tanning oils.\n\r&D");
				if(perc <= 3)
				{
					ch_printf(ch, "&R%s has hardened and become useless.\n\r&D", capitalize(skin->short_descr));
					extract_obj(skin);
				}
				break;
		}

Output from gdb
Breakpoint 1, do_tan (ch=0x1a3a5d0, argument=0x5bfca9 "") at skills.c:8472
8472    {
(gdb) info locals
skin = <value optimized out>
buf = "$n arrives from the west.\000\000.\000.\0005:49 2008 :: House of Ghouls \020\000\000\000&#65533;g\002\001\000\000\000\000v&#65533;P\000\000\000\000\000\033[0m&w[&zSat Apr  5 05:46:18 2008&W]\n\r&w<&R1361&w/&r1618 &B1205&w/&b1847 &G1436&w/&g1436 &Y698562&Wgp (&C-2507491&W) &Go"...
arg1 = "&#65533;&#65533;N;&#65533;\177\000\000}&#65533;&#65533;\001\000\000\000\000&#1061;&#65533;\001\000\000\000\000&#1061;&#65533;\001", '\0' <repeats 12 times>, "G&#65533;Q\000\000\000\000\000&#1061;&#65533;\001\000\000\000\000\235&#65533;E\000\000\000\000\000\030\000\000\0000\000\000\000&#65533;&#65533;N;&#65533;\177\000\000Sat Apr  5 05:46:18 2008\000&#65533;&#65533;\001\000\000\000\000tan\000lcome to House of Ghouls...\n\r\000\000\000\000\000\000\0001", '\0' <repeats 15 times>, "\v&#65533;&#65533;ox+\000\000\001\000\000\000\000\000\000\000&#65533;&#65533;N;&#65533;\177\000\000\fDX\000\000\000\000\000\rDX\000\000\000\000"...
(gdb) n
8478            if(IS_NPC(ch))
(gdb) n
8484            if ( ch->pcdata->learned[gsn_tan] == 0 )
(gdb) n
8490            switch( ch->substate )
(gdb) n
8539                    if ( !ch->alloc_ptr )
(gdb) n
8545                    strcpy( arg1, ch->alloc_ptr );
(gdb) n
8546                    DISPOSE( ch->alloc_ptr );
(gdb) n
8555            skin = get_obj_carry(ch, arg1);
(gdb) n
8556            separate_obj(skin);
(gdb) n
8558            if ( (number_percent() > UMAX(ch->pcdata->learned[gsn_tan], 20)  ) && !IS_IMMORTAL(ch))
(gdb) n
8560                    perc = 10;
(gdb) p perc
No symbol "perc" in current context.
Amended on Sun 06 Apr 2008 02:00 AM by Darwin
USA #6
I tried various things like changing the variable to "percent" and "i" and various other things, just to make sure there wasn't some conflict with the variable name... not likely to be the case, but I checked anyway. In all cases the variable was not in context each time I tried checking the value.

After removing the *cough* misplaced semicolons on the ends of my if statements, I went back to using number_percent() and removed the whole variable mess. So it works like I had planned it to work.
Australia Forum Administrator #7
I asked "Did you suppress optimization?" to which you didn't reply.

I notice:


skin = <value optimized out>


I suggest debugging with optimization turned off. The variable may not yet have been created.


(gdb) n
8560                    perc = 10;
(gdb) p perc
No symbol "perc" in current context.


At that point the line "perc = 10;" has not yet been executed. Try one line further on. Also try without optimization.
Australia Forum Administrator #8
With optimization on the compiler is likely to move things around, that is what optimization is about. It is hard to debug with optimization on. Also it may do things like simply store a calculation like "perc = 10" into a CPU register and not a variable, is it sees it is only used in a couple of places.

My page about using gdb, which is http://www.gammon.com.au/forum/?id=3653 suggests turning optimization off.
USA #9
Quote:
I asked "Did you suppress optimization?" to which you didn't reply.

Oh, sorry. I saw the question and completely forgot about it while I was writing my reply. I haven't changed any of the options in the makefile except for removing .c and .h files that I no longer need due to having removed snippetized features from the source. I believe that optimization is enabled in the compile flags. I'm not entirely sure what exactly that does for the executable in terms of debugging, compiled size or speed.

Here's the line from my Makefile
C_FLAGS = $(OPT_FLAG) -O -g3 -Wall -Wuninitialized $(PROF) $(NOCRYPT) $(DBUGFLG) -DSMAUG $(SOLARIS_FLAG) $(TIME) $(REG)


What does the optimization do?
Is it something that I should keep or leave it alone?
USA #10
Quote:
At that point the line "perc = 10;" has not yet been executed. Try one line further on.

The responce was the same no matter when I tried to print the value. I had initialized the variable in it's declaration at the top of the function and when that line was reached, it still gave the same "not in current context" responce. Even checking at the end of the function, it still said the same thing.
Australia Forum Administrator #11
Quote:

What does the optimization do?


Well you can optimize for speed, size, both or neither. Optimizing for speed may cause some functions to be "inlined" which means that rather than calling a function, it reproduces it inline. This can make the executable larger. Optimizing for size may do the opposite, and try to find places where you do things more than once, and do them more efficiently. Optimizing for both tries to reduce both execution time and program size.

In any case, optimized code can be hard to debug because the compiler has moved things around from what you see in the source code. See this note from the gcc manual page (my emphasis):

Quote:

Unlike most other C compilers, GCC allows you to use -g with -O. The shortcuts taken by optimized code may occasionally produce surprising results: some variables you declared may not exist at all; flow of control may briefly move where you did not expect it; some statements may not be executed because they compute constant results or their values were already at hand; some statements may execute in different places because they were moved out of loops.


Note the comment about variables not existing. :)

Now this is the description of the -O option, which you used (again, my emphasis):

Quote:

-O

Optimize. Optimizing compilation takes somewhat more time, and a lot more memory for a large function.

Without -O, the compiler’s goal is to reduce the cost of compilation and to make debugging produce the expected results. Statements are independent: if you stop the program with a breakpoint between statements, you can then assign a new value to any variable or change the program counter to any other statement in the function and get exactly the results you would expect from the source code.

With -O, the compiler tries to reduce code size and execution time, without performing any optimizations that take a great deal of compilation time.


You have used -O in the (Makefile) line you posted, although I repeatedly suggested you turn optimization off. Unless you do that, posting problems about variables not existing is not much use. Make sure you do a "make clean" afterwards to force a recompilation.

Note that it says that optimization takes time - that is the drawback, plus the problems with debugging. The plus side is your code may run slightly faster (if you optimize).
Amended on Sun 06 Apr 2008 05:34 AM by Nick Gammon
USA #12
Ah, you are a wealth of information, Nick.

I have taken your advice and removed the -O flag. I know you had suggested it before, but honestly, I didn't realize that it was optimized. Like I said, I just left all those flags the way they were when I inherited it.

I did notice that compiling didn't take as long as it had before. Also, the executable seems to be a little smaller than it was before, even with a bunch of new code added to it.

I'm not sure that optimizing for speed would yield any noticeable differences. I'll play around with it for a while and check it out.

On a side note, I have two versions of the code: one that I build on at home, and the other that is running live on a hosted site. What would your opinion be about leaving optimization on at the live version and off on my home version? Would that be ok, or would you still suggest turning optimization off there as well?
Australia Forum Administrator #13
I don't think a MUD server is a particularly CPU-intensive application, as for most of the time it is waiting for player input. Thus I don't think optimization will help a lot.

OTOH, if it ever crashes, it will be easier to debug from the core file if you have it turned off, so I would leave it off.
Australia Forum Administrator #14
It would be interesting to have you confirm that, if you remove the -O option, and recompile everything, your problem with "not in current context" goes away.
USA #15
It appears that removing the -O flag did indeed "fix" the problem. I also don't get any <value optimized out> values for any variables.
USA #16
A side effect of removing the -O flag has caused some code that previously worked to cause a crash (Segmentation fault.) Placing the -O flag back in and the code doesn't crash. This is something related to some Lua stuff I've added so I'm sure there's a way to remove the -O flag and still have it work correctly, just not sure what it is yet.
Australia Forum Administrator #17
It is likely you have used variables which were not initialized - the optimization may have caused the non-initialized value to be something that doesn't crash (eg. zero), whereas the non-initialized value in the un-optimized version may be something else (eg. -38328639764) which would cause a crash.

Make sure you have eliminated all compiler warnings, and check for that sort of thing around the part that crashes. Also use gdb to examine the core dump after the crash.
USA #18
I think that without optimization, variables on the stack get initialized to zero:


$ cat foo.c
#include <stdio.h>

int main(int argc, char** argv)
{
    int i;
    printf("%d", i);
    return 0;
}

$ gcc foo.c -o foo
$ ./foo
0
$ gcc -O2 foo.c -o foo
$ ./foo
2139865432
$   

Australia Forum Administrator #19
Stick to saying they are undefined, I think. Under Linux I got this:


$ cat test.c
#include <stdio.h>

int main()
{
    int i;
    printf("%d", i);
    return 0;
}


$ gcc test.c -Wall -o test
$ ./test
1073830176 $
USA #20
Ok, I fixed most of all the compiler warnings. I have only a few left and am not sure how these are to be fixed.
Quote:
comm.c: In function ‘new_descriptor’:
comm.c:894: warning: pointer targets in passing argument 3 of ‘accept’ differ in signedness

ident.c: In function ‘set_auth’:
ident.c:311: warning: pointer targets in passing argument 3 of ‘getsockname’ differ in signedness
ident.c:316: warning: pointer targets in passing argument 3 of ‘getpeername’ differ in signedness

lua_bits.c: In function ‘bit_tonumber’:
lua_bits.c:65: warning: pointer targets in initialization differ in signedness


The offending lines (in bold):
comm.c
    size = sizeof(sock);
    if ( check_bad_desc( new_desc ) )
    {
      set_alarm( 0 );
      return;
    }
    set_alarm( 20 );
    alarm_section = "new_descriptor::accept";
    if ( ( desc = accept( new_desc, (struct sockaddr *) &sock, &size) ) < 0 )
    {
	perror( "New_descriptor: accept" );
	sprintf(bugbuf, "[*****] BUG: New_descriptor: accept");
	log_string_plus( bugbuf, LOG_COMM, sysdata.log_level );
	set_alarm( 0 );
	return;
    }
    if ( check_bad_desc( new_desc ) )
    {
      set_alarm( 0 );
      return;
    }



ident.c
  if (getsockname(d->descriptor, (struct sockaddr *)&us, &ulen) < 0)
  {
    perror("set_auth: getsockname");
    ENDRET("Set_auth: getsockname error for %s@%s.", "(getsockname error)");
  }
  if (getpeername(d->descriptor, (struct sockaddr *)&them, &tlen) < 0)
  {
    perror("set_auth: getpeername");
    ENDRET("Set_auth: getpeername error for %s@%s.", "(getpeername error)");
  }


lua_bits.c
static int bit_tonumber (lua_State *L)
  {

  Integer result = 0;
  unsigned int base = luaL_optint(L, 2, 10);

  // get text to convert
  const unsigned char * text = luaL_checkstring (L, 1);
  const unsigned char * p;
  unsigned char c;
  unsigned int digit;
  Integer maxvalue;
  int negative = 0;
USA #21
Quote:
Stick to saying they are undefined, I think. Under Linux I got this:

How odd. I get zero no matter what, and I'm also running Linux. Perhaps it is a compiler version issue: I have gcc 4.1.2.


EDIT: it is true though that one should never depend on anything whatsoever when it comes to unsigned variables. I noticed this initialize-to-zero feature a while ago and didn't like it at the time because it seems like it can introduce subtle bugs that you don't notice until optimizations are turned on. (end edit)


Quote:
comm.c:894: warning: pointer targets in passing argument 3 of ‘accept’ differ in signedness

You took the address of an unsigned variable where the function expects a signed variable, or the other way around. Casting to the appropriate type would solve this although you might want to investigate why the signedness is different.

luaL_checkstring, for example, returns a const char*, not a const unsigned char*. Therefore the 'text' variable should not be 'unsigned' -- or, if you really mean 'unsigned' (perhaps it matters later on in the function), you need to cast the result of luaL_checkstring to const unsigned char*.
#22
Huh.

kc@persephone:~/Desktop$ gcc --version
gcc (GCC) 4.1.3 20070929 (prerelease) (Ubuntu 4.1.2-16ubuntu2)
Copyright (C) 2006 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

kc@persephone:~/Desktop$ cat test.c
#include <stdio.h>

int main() {
    int i;
    printf("%d\n", i);
    return 0;
}

kc@persephone:~/Desktop$ gcc test.c -Wall -o test
kc@persephone:~/Desktop$ ./test
-1209006080
kc@persephone:~/Desktop$ ./test
-1208350720
kc@persephone:~/Desktop$ ./test
-1209026560
kc@persephone:~/Desktop$ gcc -O2 test.c -Wall -o test
test.c: In function 'main':
test.c:5: warning: 'i' is used uninitialised in this function
kc@persephone:~/Desktop$ ./test
1
kc@persephone:~/Desktop$ ./test
1
kc@persephone:~/Desktop$ ./test
1
kc@persephone:~/Desktop$ 


Wouldn't it be a compiler/target option somewhere?
Australia Forum Administrator #23
Quote:

it is true though that one should never depend on anything whatsoever when it comes to unsigned variables


You mean, "undefined variables".
Australia Forum Administrator #24
Quote:

Wouldn't it be a compiler/target option somewhere?


I can't see one, and in any case I don't think the correctness of a C program should depend on the compile-time options.

Basically it is incorrect to assume that uninitialized automatic variables (those on the stack) have anything but undefined values.

As you can see from the various tests, you may well get consistent results from one test to the next, and under certain circumstances, the value may be zero, but you can't assume that.

See: http://c-faq.com/decl/initval.html

I quote (my emphasis):

Quote:

Variables with automatic duration (i.e. local variables without the static storage class) start out containing garbage, unless they are explicitly initialized. (Nothing useful can be predicted about the garbage.)

USA #25
What's interesting to me is that for as long as I can remember, developing on this and other similar Linux systems, every single time I've looked at uninitialized stack variables in the debugger, they've been zero. And when optimization was turned on, they were garbage.

This behavior shouldn't be depended upon (of course) but it's interesting that it was so consistent for me...

IIRC, the Visual Studio libraries have different initialization behavior for the debug and optimized versions, but I haven't played with those for quite a long time.
Amended on Mon 07 Apr 2008 06:29 AM by Nick Gammon
#26
Quote:
I can't see one, and in any case I don't think the correctness of a C program should depend on the compile-time options.


It's not so much the correctness of the program as the dialect of C you are forcing the compiler to expect, which can be altered by compile time options (and pragmas &c..). For.ex. there are target options to control whether a 'long long' is 32 or 64 bits wide.

Quote:
What's interesting to me is that for as long as I can remember, developing on this and other similar Linux systems, every single time I've looked at uninitialized stack variables in the debugger, they've been zero. And when optimization was turned on, they were garbage.


Debuggers are famous for 'fixing' problems that occur outside of the debugger by doing things like zeroing variables.
USA #27
Quote:
You took the address of an unsigned variable where the function expects a signed variable, or the other way around. Casting to the appropriate type would solve this although you might want to investigate why the signedness is different.
Well, actually, I didn't do any of those. They're all parts of stock code.

Quote:
luaL_checkstring, for example, returns a const char*, not a const unsigned char*. Therefore the 'text' variable should not be 'unsigned' -- or, if you really mean 'unsigned' (perhaps it matters later on in the function), you need to cast the result of luaL_checkstring to const unsigned char*.


Changing the 'text' variable to const char * instead of const unsigned char * yields an error when compiling. That part is from Nick's Lua code
lua_bits.c: In function ‘bit_tonumber’:
lua_bits.c:79: warning: pointer targets in assignment differ in signedness
lua_bits.c:108: error: invalid operands to binary -


Line 108 is bolded.
static int bit_tonumber (lua_State *L)
  {

  Integer result = 0;
  unsigned int base = luaL_optint(L, 2, 10);

  // get text to convert
  const char * text = luaL_checkstring (L, 1);
  const unsigned char * p;
  unsigned char c;
  unsigned int digit;
  Integer maxvalue;
  int negative = 0;

  if (base != 10)
    luaL_argcheck(L, 2 <= base && base <= 36, 2, "base out of range");

  // maximum value before multiply by the base
  maxvalue = 4503599627370496 / base;   // 2^52

  // skip whitespace
  for (p = text; isspace (*p); p++)
    ;  // skip leading spaces

  // look for sign
  if (*p == '-')
    {
    negative = 1;
    p++;
    }
  else if (*p == '+')
    p++;


  while ((c = *p++))
    {

    // The largest mantissa a double can have it 52 bits
    if (result >= maxvalue)
      luaL_error (L, "Number too big");

    if (isdigit (c))
      digit = c - '0';
    else if (isalpha (c))
      digit = toupper (c) - 'A' + 10;
    else
      digit = 999;   // bad digit - force error in next line

    if (digit >= base)
      luaL_error (L, "Bad digit ('%c') at position %i",
                  c, (int) (p - text));

    result = result * base + digit;
    } // end while

  if (negative)
    result = -result;

  lua_pushnumber (L, result);

  return 1;  // number of result fields
  } // end of bit_tonumber
USA #28
Quote:
Well, actually, I didn't do any of those. They're all parts of stock code.

Well, by "you" I meant the code, not you personally. :-)

It's quite possible that this compiled fine for Nick when he wrote it but gives you warnings due to different compiler versions etc.

Quote:
lua_bits.c:108: error: invalid operands to binary -

Well, since 'text' is now signed, you want 'p' to also be signed. I bet that is what the warning on line 79 is from:
for (p = text; isspace (*p); p++)
Australia Forum Administrator #29
Did you search the forum for this error, about "differ in signedness"?

See this page:

http://www.gammon.com.au/forum/bbshowpost.php?id=8000&page=2

My response at the time was:


Anyway, the bits file is not really required yet - I didn't use it for the initial stuff, and so far have only used it in one place to "and" a room flag.

I would remove it from the makefile for now, and remove the reference to it in the lua_scripting.c file.


In other words, this function isn't required. I just copied it over from MUSHclient, and due to compiler differences you are getting the error.

Also see this post:

http://www.gammon.com.au/forum/bbshowpost.php?id=8343

I also suggested there simply removing the bit_tonumber function as it isn't required, and we are wasting time fixing something that isn't actually used.
USA #30
Alrighty, fixed those last two warnings and removed references to lua_bits.c. Recompiled after 'make clean' and everything looks good.