Memory corruption bug, how to find it?

Hi,
I'm writing a display library for an 'unusual' (bistable cholesteric) graphics display... and have started to encounter 'weird' behavior (locking up, repeating a serial character over and over, etc.) that is almost certainly a memory corruption issue. Running the attached test sketch & library exactly as shown (the exact behavior is sensitive to physical code placement) on a Duemilanove with 0022 flushes out at least one clear case (note a character in the "String addr:" line is replaced by garbage):

Hello
set font
turn on
reset
clearing
set cursor
print (rom)
S~ring addr:24E
Got byte: HGot byte: eGot byte: lGot byte: lGot byte: oGot byte: ,Got byte:  Got byte: wGot byte: oGot byte: rGot byte: lGot byte: dGot byte: !print
print #
 write:0Got byte: 0

The most recent additions (before I started noticing effects of the bug) are that the library now subclasses Arduino's print(), the accompanying write() method, and the text generation functions (putch(), puts(), puts_P()), but I can't see any obvious memory-smashing bug in them.

Trying to isolate it by removing code/calls until it 'goes away', or printing out the memory contents / more debug data is unreliable, since even a few-byte change alters the behavior to effectively hide the problem. And I can't find a way to generate a map file or meaningful error messages (-Wall?) while in Arduino-land, it looks like the compiler arguments (and "avr-g++.exe" itself?) are hardcoded in the IDE, and building a sketch of any complexity via e.g. Makefile under win32 is nontrivial. Does anyone know a good way to go about debugging this?

(I'm a bit (er, lot) of a c++ noob... does using Serial's print() from inside a library that subclasses print() pose any problems?)

The most recently added code. See attachment for complete library and sketch testcase...:

[...]

void NVLCD::putch(byte c)
{
     Serial.print("Got byte: ");
     Serial.print(c);
     // only supporting fixed size font for now
     //if( font[FONT_LENGTH] == 0 && font[FONT_LENGTH+1] == 0)
     {

        // FIXME: Precompute all this stuff when initially setting a font
        byte width = pgm_read_byte(&font[FONT_FIXED_WIDTH]);
        byte height = pgm_read_byte(&font[FONT_HEIGHT]);
        byte first_char = pgm_read_byte(&font[FONT_FIRST_CHAR]);

        //Serial.print("\nWidth: ");
        //Serial.print(width, HEX);
        //Serial.print("  Height: ");
        //Serial.print(height, HEX);

	byte line=0;
	byte temp=0;


        if(c >= first_char)
        {
           // resolve ASCII value to character index in font
           c = c - first_char;

           // index the *byte* in font corresponding to this index
           // NB: fonts can be > 8 pixels tall and/or wide.

           byte char_width_bytes = width; // 1 byte -> 1 col (8 px vertical)
           byte char_height_bytes = (height+7) / 8; // round up

           //byte char_total_bytes

           // >8 wide -> (char, char+1, ... in font data)
           // >8 tall -> multiple lines (char, char+1, ... in font data)

           // First byte: c * (char_width_bytes * char_height_bytes)
           for(int row=0; row < char_height_bytes; row++)
           {
              // FIXME: advance cursor column here if multiline fonts...
              for(int col=0; col < width; col++)
              {
                //Serial.print("row=");
                //Serial.print(row, DEC);
                //Serial.print("  col=");
                //Serial.print(col, DEC);
                int k = (c * (char_width_bytes * char_height_bytes) + (row * char_height_bytes) + col)    + 6;
                //Serial.print("  idx=");
                //Serial.print(k, DEC);

                byte cha = pgm_read_byte(&font[k]);
                //Serial.print("  val=");
                //Serial.print(cha, HEX);
                //Serial.println(" ");
                lcd_write(cha);
              }
           }

        }

     }

}

void NVLCD::putch_P(PGM_P pc)
{

}

void NVLCD::puts(const char * s)
{
     // print a string from RAM
  Serial.print("String addr:");
  Serial.print((long int)s, HEX);
  Serial.println();
    set_seg_mapping();
    lcd_mode_data();
    while(*s != 0)
    {
       putch(*s);
       //*s++;
       s++;
    }
}




void NVLCD::puts_P(PGM_P s)
{
     // print a string from code memory
    byte c;
  Serial.print("String addr:");
  Serial.print((long int)s, HEX);
  Serial.println();

    set_seg_mapping();
    lcd_mode_data();
    while((c = pgm_read_byte(s)) != 0)
    {
       putch(c);
       s++;
    }
}

// stolen from GLCD
/*
 * needed to resolve virtual print functions
 */
void NVLCD::write(uint8_t c) // for Print base class
{
     Serial.print(" write:");
     Serial.print(c);
     putch(c);
}

testcase.zip (10.3 KB)

I can't see anything obvious in it. The compile used 4880 of PROGMEM so that is hardly the issue. You don't appear to be doing a malloc, unless I missed it. Why are you checking free RAM? Is something going to use it that I haven't spotted? My test shows Free RAM: 1699 so that looks OK.

Testing on my Uno does not reveal any corruption of the type you describe.

Another possibility to investigate is uninitialized data elements (eg. using a variable without initializing it). That would account for it being very sensitive to small changes in code (different register being allocated for example).

As for the -Wall that would be great. Unfortunately it appears it isn't easy to do. I have kept suggesting an option to have that, but been largely ignored. Apparently someone has rebuilt the IDE with an option to do it (an unofficial build), I don't remember exactly where that is right now. That may well point to the problem.

I don't see why you can't do Serial.print in your code to try to narrow it down.

No malloc() in use anywhere, at least by me. I won't comment on those who do use dynamic memory allocation on an 8-bit micro :wink:

I put the memory check in just to rule it out. Although, on the code posted, rebuilding with the (currently unused) memory check function commented out just causes the sketch to output

Hello
HeHeHeHeHeHeHeHeHeHeHeHeHeHeHeHeHeHeHeHeHeHeHeHeHe...

ad infinitum. I swear I'm not making this up. As I scrounge for the bug the board is literally laughing at me! :blush:

Thanks! I'll see if I can dig this up, my foray last night into getting it to build via makefile failed miserably.

Issues like the above! Mostly, now that I have a reproducible and visible manifestation of the bug I'm kind of afraid to change anything, since something as simple as declaring another variable rejuggles the memory contents enough to (I'm assuming) cause the corrupted byte to fall off the edge of the string it's currently appearing in and effectively go into hiding. Failing access to a hacked Arduino build with warnings and mapfile enabled, I'll try putting in a simple 'talker' function that can be called at different points to dump the entire memory contents over the serial port - comparing them for changes may at least tell me the physical address(es) being hit and when, assuming the talker itself doesn't crash :wink:

I've had similar problem just yesterday when compiling outside the IDE, I swapped from a 328 to a 2560 but I was still using the .o files from the 328. It compiled and even ran a bit but the following

setup () {
   Serial.begin (115200);
   Serial.print ("123456789");
}

Printed this for ever

12345123451234512345

It turned out the code was constantly reseting because the linked .o files were for another processor.


Rob

Is this the hacked IDE you mentioned?
Files: MostlySoftware.com is for sale | HugeDomains
Discussion thread: Loading...

Going to give this a go when it finishes downloading.

I tried the 'talker' approach just now, with inconclusive results. It turns out nearly the entire memory contents are nonzero (I suspect whatever function copies 'initialized' vars to RAM stops there rather than explicitly zero the unused bytes), and a surprisingly large number of memory locations change between successive calls to even minor functions like Serial.print() (and likely the talker itself, heh)... I assume these are mainly internal timer/counter values (like Arduino's interrupt-driven uptime counter), various function-local variables modified by calls, etc., but without a mapfile, determining which changes are meaningful is still kind of stumbling around in the dark. Needless to say adding the talker loop made the '~' (0x7E) character disappear, and this value does not occur in any of the changed locations. Possible evidence this is address-specific rather than value specific, and probably stems from my general inability to use pointers properly :wink:

I was thinking of this:

http://arduino.cc/forum/index.php/topic,54456.0.html

Looks like it is the same change.

As for the non-zero memory, you could adapt the "freemem" function to zero the memory. After all it does a malloc until it can't do any more. Well do a calloc instead, which zeroes the allocated memory. By the time it has got its largest block the memory should be (largely anyway) zeroed.

Thanks Nick,
I assume 'freemem' is a library function; can you tell me where it's located? Trying to Google it just turns up piles of Win32 shareware apps claiming to free up memory on PCs.

Some digging today and it gets curiouser and curiouser... I managed to make the error 'visibly' reproducible again while having the talker loop and mem-reporting function in place. If I go below 104 bytes free memory (as reported by 'FreeRam' function shown), the program crashes in interesting ways. Just above that, I can get a corrupt byte to appear in the middle of a large text string, and have narrowed down its appearance to calling the 'nvlcd' constructor itself!

The constructor is pretty straightforward: store user-supplied pin #s to variables, setup those pins... here is the only remotely interesting part:

       //Check if SPI has already been initialized and begin() it if not.
       if(!(SPCR & _BV(SPE)))
       {
          // SPI not initialized yet; set it up for our display

          SPI.begin();
          SPI.setBitOrder(MSBFIRST);
          SPI.setDataMode(0);
          SPI.setClockDivider(SPI_CLOCK_DIV4);
       }

Does doing this from inside the library pose any problems? Now that I have -Wall enabled, I can see why the Arduino team is reluctant to give this option :wink: but it doesn't give any warnings about this code.

Currently I have been #include-ing SPI.h in the sketch as well as in the library. The first thing SPI.cpp does is declare an instance of SPI: "SPIClass SPI;". I'm a bit (lot) rusty on my C++ - does this cause two separate instances of SPI to exist, which just happens to work because they are thin wrappers around the same hardware registers?

The even more curiouser part: If I remove the include from the sketch, I get an error that suddenly claims the SPI.h called for in the library cannot be found. There is no such error if SPI.h is included from the sketch. SPI is only used in the library and not the sketch, so this bugs me a little (no pun intended).

C:\Documents and Settings\Tim\My Documents\Arduino\libraries\nvlcd\nvlcd.cpp:4:17: error: SPI.h: No such file or directory

As of Arduino 0019, the Ethernet library depends on the SPI library.
You appear to be using it or another library that depends on the SPI library.C:\Documents and Settings\Tim\My Documents\Arduino\libraries\nvlcd\nvlcd.cpp: In member function 'void NVLCD::init()':
C:\Documents and Settings\Tim\My Documents\Arduino\libraries\nvlcd\nvlcd.cpp:180: error: 'SPI' was not declared in this scope
C:\Documents and Settings\Tim\My Documents\Arduino\libraries\nvlcd\nvlcd.cpp:183: error: 'SPI_CLOCK_DIV4' was not declared in this scope
(...etc....)

The new testcase is attached, and if run untouched on Arduino 0022 / Atmega168 with default compiler options, "should" reproducibly write an 0xFF over the large filler string, at address 0x18B. Note I did find & fix an unrelated bug in the'testimage.c' data, but to my knowledge it doesn't actually change the code (superfluous initializer bytes). Adding or removing as much as 1 byte from the string can cause the behavior to disappear, though.

(Probably not helpful, but the talker output just after NVLCD() call:)

0: C6 00 5B 04 10 00 A1 04 02 00 E1 04 0B 15 09 42 
10: 05 02 05 00 30 00 00 03 05B 04 C0 00 1D 00 C6 00 
20: 00 00 00 0C 2E 04 00 00 00 01 31 01 00 00 00 00 
30: 00 00 00 00 00 06 07 07 00 00 00 00 00 00 00 00 
40: 00 4F 01 00 03 03 6F 00 00 00 00 00 50 00 00 00 
50: 10 00 00 00 07 00 00 00 00 00 00 00 00 DC 04 94 
60: 00 00 00 00 00 00 9C 00 00 00 00 00 00 00 01 00 
70: 00 00 00 00 00 00 00 00 00 00 87 00 00 00 00 00 
80: 01 03 00 00 59 00 00 00 00 00 00 00 00 00 00 00 
90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
A0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
B0: 01 04 5A 00 00 00 00 00 00 F8 FE FF 00 00 00 00 
C0: 02 98 06 00 CF 00 00 00 00 00 00 00 00 00 00 00 
D0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
E0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
F0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
100: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
110: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
120: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
130: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
140: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
150: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
160: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
170: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
180: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E FF 2E 2E 2E 2E 
190: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
1A0: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
1B0: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
1C0: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
1D0: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
1E0: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
1F0: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
200: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
210: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
220: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
230: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
240: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
250: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
260: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
270: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
280: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
290: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
2A0: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
2B0: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
2C0: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
2D0: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
2E0: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
2F0: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
300: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
310: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 
320: 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 2E 00 0A 00 
330: 3A 20 00 30 00 20 00 0A 0A 00 46 72 65 65 20 52 
340: 41 4D 3A 20 00 48 65 6C 6C 6F 00 73 65 74 20 66 
350: 6F 6E 74 00 74 75 72 6E 20 6F 6E 00 72 65 73 65 
360: 74 00 63 6C 65 61 72 69 6E 67 00 73 65 74 20 63 
370: 75 72 73 6F 72 00 70 72 69 6E 74 20 28 72 6F 6D 
380: 29 00 70 72 69 6E 74 00 70 72 69 6E 74 20 23 00 
390: 47 6F 74 20 62 79 74 65 3A 20 00 20 77 72 69 74 
3A0: 65 3A 00 53 74 72 69 6E 67 20 61 64 64 72 3A 00 
3B0: 0D 00 00 00 00 00 1C 04 E3 07 FB 07 00 00 00 00 
3C0: 8F 07 E3 07 FB 07 34 07 5F 07 45 07 83 07 27 1E 
3D0: 00 00 F3 1E 00 00 03 00 00 00 00 00 00 00 00 00 
3E0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
3F0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
400: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
410: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
420: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
430: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
440: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
450: 00 00 00 00 00 00 00 00 00 00 00 C0 03 D7 03 C5 
460: 00 C4 00 C0 00 C1 00 C6 00 04 03 07 05 01 00 00 
470: 00 00 05 00 03 00 07 9A 08 03 3C 03 9C 00 01 00 
480: 03 FF 00 C0 00 00 00 C0 00 02 00 05 00 C0 00 00 
490: 97 05 02 00 00 07 97 08 F0 04 A0 04 A1 04 A2 08 
4A0: 9D 0D 09 EF B3 E3 E9 5D 9A F8 3E FB 5B FF FC 3B 
4B0: 00 C0 04 5B 00 10 00 C6 00 08 35 5B 04 00 00 04 
4C0: C0 C1 04 00 00 00 00 15 00 C0 00 02 00 05 14 00 
4D0: 00 07 9A 07 F3 04 D6 02 05 08 21 01 B9 04 E0 02 
4E0: 27 B6 03 68 00 00 01 04 05 09 0B D7 F8 16 00 00 
4F0: 00 00 01 B6 02 02 0F 41 15 C4 04 04 07 E0 01 82

testcase2.zip (10.5 KB)

Drmn4ea:
I assume 'freemem' is a library function; can you tell me where it's located? Trying to Google it just turns up piles of Win32 shareware apps claiming to free up memory on PCs.

FreeRam I meant - the function that is already in your code. Actually now that I look at it :grin: it is different to what I expected. There was another one recently that just kept allocating memory until it couldn't. But you could take the value returned from FreeRam, allocate a block that large (or maybe slightly smaller), zero it all out, and then free it. That should set all "free" memory to a known state. Whether that achieves anything or not remains to be seen.

Drmn4ea:
Does doing this from inside the library pose any problems?

I don't think so.

Currently I have been #include-ing SPI.h in the sketch as well as in the library. The first thing SPI.cpp does is declare an instance of SPI: "SPIClass SPI;". I'm a bit (lot) rusty on my C++ - does this cause two separate instances of SPI to exist, which just happens to work because they are thin wrappers around the same hardware registers?

The SPI object does have buffers in it but as far as I can see they are static buffers so they should be shared. I wouldn't particularly want to have two copies, though, it seems unnecessary.

The even more curiouser part: If I remove the include from the sketch, I get an error that suddenly claims the SPI.h called for in the library cannot be found. There is no such error if SPI.h is included from the sketch. SPI is only used in the library and not the sketch, so this bugs me a little (no pun intended).

That's a "feature" of the way the IDE works. It copies the .h files to a temporary directory for compiling, and it uses the include directives in the main sketch to know which ones to copy.

As of Arduino 0019, the Ethernet library depends on the SPI library.

Yeah, I've complained about this before.

The new testcase is attached, and if run untouched on Arduino 0022 / Atmega168 with default compiler options, "should" reproducibly write an 0xFF over the large filler string, at address 0x18B. Note I did find & fix an unrelated bug in the'testimage.c' data, but to my knowledge it doesn't actually change the code (superfluous initializer bytes). Adding or removing as much as 1 byte from the string can cause the behavior to disappear, though.

Without looking at all your code I'm inclined to guess an unitialized variable is responsible. Did any of the warnings go on about something like that?

You could download WinAVR, then, according to the AVR-Libc manual:

11.22 How to detect RAM memory and variable overlap problems?
You can simply run avr-nm on your output (ELF) file. Run it with the -n option, and
it will sort the symbols numerically (by default, they are sorted alphabetically).
Look for the symbol _end, that’s the first address in RAM that is not allocated by
a variable. (avr-gcc internally adds 0x800000 to all data/bss variable addresses, so
please ignore this offset.) Then, the run-time initialization code initializes the stack
pointer (by default) to point to the last available address in (internal) SRAM. Thus, the
region between _end and the end of SRAM is what is available for stack. (If your
application uses malloc(), which e. g. also can happen inside printf(), the heap for
dynamic memory is also located there. See Memory Areas and Using malloc().)
The amount of stack required for your application cannot be determined that easily.
For example, if you recursively call a function and forget to break that recursion, the
amount of stack required is infinite. :slight_smile: You can look at the generated assembler code
(avr-gcc ... -S), there’s a comment in each generated assembler file that tells
you the frame size for each generated function. That’s the amount of stack required for
this function, you have to add up that for all functions where you know that the calls
could be nested.

On my Win XP machine, Arduino creates a folder like C:\Documents and Settings<name>\Local Settings\Temp\buildnnnnnnnnnnnnnnnn.tmp and the .elf file is there.

Of course this doesn't tell you anything about what might happen once the program starts running. I didn't notice if a link was given above for the memoryTest() function, so here is one. Note that there is some discussion and also alternate approaches.

When I looked at the original sketch it appeared to have a lot of free memory, and did not do any memory allocation. My suspicion is un-initialized variables.

[quote author=Nick Gammon link=topic=60252.msg436985#msg436985 date=1304817518]
My suspicion is un-initialized variables.[/quote]

Following from the AVR Libc User Manual. Of course this is not to say, for example, that zero is the proper initial value for any particular integer variable; obviously that depends on the application. Maybe that's what you were getting at, Nick.

11.8 Shouldn’t I initialize all my variables?
Global and static variables are guaranteed to be initialized to 0 by the C standard.
avr-gcc does this by placing the appropriate code into section .init4 (see The .initN
Sections). With respect to the standard, this sentence is somewhat simplified (because
the standard allows for machines where the actual bit pattern used differs from all bits
being 0), but for the AVR target, in general, all integer-type variables are set to 0, all
pointers to a NULL pointer, and all floating-point variables to 0.0.
As long as these variables are not initialized (i. e. they don’t have an equal sign and
an initialization expression to the right within the definition of the variable), they go
into the .bss section of the file. This section simply records the size of the variable,
but otherwise doesn’t consume space, neither within the object file nor within flash
memory. (Of course, being a variable, it will consume space in the target’s SRAM.)
In contrast, global and static variables that have an initializer go into the .data section
of the file. This will cause them to consume space in the object file (in order to record
the initializing value), and in the flash ROM of the target device. The latter is needed
since the flash ROM is the only way that the compiler can tell the target device the
value this variable is going to be initialized to.
Now if some programmer "wants to make doubly sure" their variables really get a 0
at program startup, and adds an initializer just containing 0 on the right-hand side,
they waste space. While this waste of space applies to virtually any platform C is
implemented on, it’s usually not noticeable on larger machines like PCs, while the
waste of flash ROM storage can be very painful on a small microcontroller like the
AVR.
So in general, variables should only be explicitly initialized if the initial value is nonzero.
Note:
Recent versions of GCC are now smart enough to detect this situation, and revert
variables that are explicitly initialized to 0 to the .bss section. Still, other compilers
might not do that optimization, and as the C standard guarantees the initialization,
it is safe to rely on it.

Ah, very interesting, thank you.

However I was referring to non-static variables (auto variables). Here, in his code for example:

void NVLCD::commit(int x1, int y1, int x2, int y2)
{

...

  byte DrivePW;
  byte DriveV;
  byte VAClearPW;
  byte VAIdle;
  byte AAClearPW;
  byte AAIdle;
  byte ClearV;

  byte offset;
  byte startLineCmd;

Now sure, they may be correctly initialized before being used, I haven't gone through line-by-line. And the -Wall tends to warn about that. But if there is a code branch where one of those is used whilst still having undefined data in it, that could be the problem.

So in general, variables should only be explicitly initialized if the initial value is nonzero.

I can't help thinking that style-wise this looks bad. For example:

int a = 42;
int b;       
int c = 84;

Especially if you decide "ah these don't need to be at global scope, I'll cut and paste them into a function" - and you overlook the assumed zero initialization.

Right, right. True enough!

@Nick:
No uninitialized variables in the contsructor or anything it calls (although certain constants used in the commit() function (not used in these examples) as-written could be uninitialized in certain cases).

There are plenty of warnings along the lines "only initialized variables can be placed into program memory area", but from what I've read this is a spurious message.

This is the actual compiler output with -Wall:

In file included from nvlcd_corrupttest.cpp:8:
J:\arduino-0022-hack\libraries\SPI/SPI.h:25:1: warning: "SPI_CLOCK_DIV64" redefined
J:\arduino-0022-hack\libraries\SPI/SPI.h:20:1: warning: this is the location of the previous definition
In file included from nvlcd_corrupttest.cpp:5:
C:\Documents and Settings\Tim\My Documents\Arduino\libraries\SystemFont5x7/SystemFont5x7.h:48: warning: only initialized variables can be placed into program memory area
nvlcd_corrupttest.cpp: In function 'void setup()':
nvlcd_corrupttest.cpp:73: warning: only initialized variables can be placed into program memory area
In file included from C:\Documents and Settings\Tim\My Documents\Arduino\libraries\SystemFont5x7\SystemFont5x7.cpp:3:
C:\Documents and Settings\Tim\My Documents\Arduino\libraries\SystemFont5x7\/SystemFont5x7.h:48: warning: only initialized variables can be placed into program memory area
In file included from J:\arduino-0022-hack\libraries\SPI\SPI.cpp:12:
J:\arduino-0022-hack\libraries\SPI\/SPI.h:25:1: warning: "SPI_CLOCK_DIV64" redefined
J:\arduino-0022-hack\libraries\SPI\/SPI.h:20:1: warning: this is the location of the previous definition
In file included from C:\Documents and Settings\Tim\My Documents\Arduino\libraries\nvlcd\nvlcd.cpp:4:
J:\arduino-0022-hack\libraries\SPI/SPI.h:25:1: warning: "SPI_CLOCK_DIV64" redefined
J:\arduino-0022-hack\libraries\SPI/SPI.h:20:1: warning: this is the location of the previous definition
C:\Documents and Settings\Tim\My Documents\Arduino\libraries\nvlcd\nvlcd.cpp: In member function 'void NVLCD::commit(int, int, int, int)':
C:\Documents and Settings\Tim\My Documents\Arduino\libraries\nvlcd\nvlcd.cpp:320: warning: comparison between signed and unsigned integer expressions
C:\Documents and Settings\Tim\My Documents\Arduino\libraries\nvlcd\nvlcd.cpp: In member function 'void NVLCD::putch(byte)':
C:\Documents and Settings\Tim\My Documents\Arduino\libraries\nvlcd\nvlcd.cpp:555: warning: unused variable 'line'
C:\Documents and Settings\Tim\My Documents\Arduino\libraries\nvlcd\nvlcd.cpp:556: warning: unused variable 'temp'
C:\Documents and Settings\Tim\My Documents\Arduino\libraries\nvlcd\nvlcd.cpp: In member function 'void NVLCD::commit(int, int, int, int)':
C:\Documents and Settings\Tim\My Documents\Arduino\libraries\nvlcd\nvlcd.cpp:307: warning: 'ClearV' may be used uninitialized in this function
C:\Documents and Settings\Tim\My Documents\Arduino\libraries\nvlcd\nvlcd.cpp:306: warning: 'AAIdle' may be used uninitialized in this function
C:\Documents and Settings\Tim\My Documents\Arduino\libraries\nvlcd\nvlcd.cpp:305: warning: 'AAClearPW' may be used uninitialized in this function
C:\Documents and Settings\Tim\My Documents\Arduino\libraries\nvlcd\nvlcd.cpp:304: warning: 'VAIdle' may be used uninitialized in this function
C:\Documents and Settings\Tim\My Documents\Arduino\libraries\nvlcd\nvlcd.cpp:303: warning: 'VAClearPW' may be used uninitialized in this function
C:\Documents and Settings\Tim\My Documents\Arduino\libraries\nvlcd\nvlcd.cpp:302: warning: 'DriveV' may be used uninitialized in this function
C:\Documents and Settings\Tim\My Documents\Arduino\libraries\nvlcd\nvlcd.cpp:301: warning: 'DrivePW' may be used uninitialized in this function
In file included from J:\arduino-0022-hack\hardware\arduino\cores\arduino\/wiring_private.h:30,
                 from J:\arduino-0022-hack\hardware\arduino\cores\arduino\pins_arduino.c:26:
j:/arduino-0022-hack/hardware/tools/avr/lib/gcc/../../avr/include/avr/delay.h:36:2: warning: #warning "This file has been moved to <util/delay.h>."
J:\arduino-0022-hack\hardware\arduino\cores\arduino\pins_arduino.c:361: warning: initialization makes integer from pointer without a cast
J:\arduino-0022-hack\hardware\arduino\cores\arduino\pins_arduino.c:362: warning: initialization makes integer from pointer without a cast
J:\arduino-0022-hack\hardware\arduino\cores\arduino\pins_arduino.c:363: warning: initialization makes integer from pointer without a cast
J:\arduino-0022-hack\hardware\arduino\cores\arduino\pins_arduino.c:369: warning: initialization makes integer from pointer without a cast
J:\arduino-0022-hack\hardware\arduino\cores\arduino\pins_arduino.c:370: warning: initialization makes integer from pointer without a cast
J:\arduino-0022-hack\hardware\arduino\cores\arduino\pins_arduino.c:371: warning: initialization makes integer from pointer without a cast
J:\arduino-0022-hack\hardware\arduino\cores\arduino\pins_arduino.c:377: warning: initialization makes integer from pointer without a cast
J:\arduino-0022-hack\hardware\arduino\cores\arduino\pins_arduino.c:378: warning: initialization makes integer from pointer without a cast
J:\arduino-0022-hack\hardware\arduino\cores\arduino\pins_arduino.c:379: warning: initialization makes integer from pointer without a cast
In file included from J:\arduino-0022-hack\hardware\arduino\cores\arduino\/wiring_private.h:30,
                 from J:\arduino-0022-hack\hardware\arduino\cores\arduino\WInterrupts.c:34:
j:/arduino-0022-hack/hardware/tools/avr/lib/gcc/../../avr/include/avr/delay.h:36:2: warning: #warning "This file has been moved to <util/delay.h>."
In file included from J:\arduino-0022-hack\hardware\arduino\cores\arduino\/wiring_private.h:30,
                 from J:\arduino-0022-hack\hardware\arduino\cores\arduino\wiring.c:25:
j:/arduino-0022-hack/hardware/tools/avr/lib/gcc/../../avr/include/avr/delay.h:36:2: warning: #warning "This file has been moved to <util/delay.h>."
In file included from J:\arduino-0022-hack\hardware\arduino\cores\arduino\/wiring_private.h:30,
                 from J:\arduino-0022-hack\hardware\arduino\cores\arduino\wiring_analog.c:27:
j:/arduino-0022-hack/hardware/tools/avr/lib/gcc/../../avr/include/avr/delay.h:36:2: warning: #warning "This file has been moved to <util/delay.h>."
In file included from J:\arduino-0022-hack\hardware\arduino\cores\arduino\/wiring_private.h:30,
                 from J:\arduino-0022-hack\hardware\arduino\cores\arduino\wiring_digital.c:27:
j:/arduino-0022-hack/hardware/tools/avr/lib/gcc/../../avr/include/avr/delay.h:36:2: warning: #warning "This file has been moved to <util/delay.h>."
In file included from J:\arduino-0022-hack\hardware\arduino\cores\arduino\/wiring_private.h:30,
                 from J:\arduino-0022-hack\hardware\arduino\cores\arduino\wiring_pulse.c:25:
j:/arduino-0022-hack/hardware/tools/avr/lib/gcc/../../avr/include/avr/delay.h:36:2: warning: #warning "This file has been moved to <util/delay.h>."
In file included from J:\arduino-0022-hack\hardware\arduino\cores\arduino\/wiring_private.h:30,
                 from J:\arduino-0022-hack\hardware\arduino\cores\arduino\wiring_shift.c:25:
j:/arduino-0022-hack/hardware/tools/avr/lib/gcc/../../avr/include/avr/delay.h:36:2: warning: #warning "This file has been moved to <util/delay.h>."
In file included from J:\arduino-0022-hack\hardware\arduino\cores\arduino\/wiring_private.h:30,
                 from J:\arduino-0022-hack\hardware\arduino\cores\arduino\HardwareSerial.cpp:28:
j:/arduino-0022-hack/hardware/tools/avr/lib/gcc/../../avr/include/avr/delay.h:36:2: warning: #warning "This file has been moved to <util/delay.h>."
J:\arduino-0022-hack\hardware\arduino\cores\arduino\Print.cpp: In member function 'void Print::print(const String&)':
J:\arduino-0022-hack\hardware\arduino\cores\arduino\Print.cpp:48: warning: comparison between signed and unsigned integer expressions
J:\arduino-0022-hack\hardware\arduino\cores\arduino\Tone.cpp:108: warning: only initialized variables can be placed into program memory area
Binary sketch size: 5662 bytes (of a 14336 byte maximum)

Unfortunately I don't see anything too bothersome in there.

@Jack:
I'm not closer to finding this bug (yet), but it looks like I have a bit more PIC18 compiler induced brain damage to unlearn. The stack not being an 'allocated' block under avr-gcc (thus not counted in the FreeRam()) would explain the behavior with <104-byte cushion.

I actually did find the stuff on nm last night (I guess what I was calling a mapfile above is known as a symbol table to the rest of the free world :wink: , but its output wasn't very helpful. I was hoping for something more like the PIC compiler .map file, which lists the base address and length of every variable individually. (Of course, their compiler is a bit braindead in many ways, statically allocating local variables being one of them; maybe avr-gcc uses the stack and/or AVR's spacious registers for everything and there is no known variable address to print in the first place!)

Drmn4ea:

C:\Documents and Settings\Tim\My Documents\Arduino\libraries\nvlcd\nvlcd.cpp: In member function 'void NVLCD::commit(int, int, int, int)':

C:\Documents and Settings\Tim\My Documents\Arduino\libraries\nvlcd\nvlcd.cpp:307: warning: 'ClearV' may be used uninitialized in this function
C:\Documents and Settings\Tim\My Documents\Arduino\libraries\nvlcd\nvlcd.cpp:306: warning: 'AAIdle' may be used uninitialized in this function
C:\Documents and Settings\Tim\My Documents\Arduino\libraries\nvlcd\nvlcd.cpp:305: warning: 'AAClearPW' may be used uninitialized in this function
C:\Documents and Settings\Tim\My Documents\Arduino\libraries\nvlcd\nvlcd.cpp:304: warning: 'VAIdle' may be used uninitialized in this function
C:\Documents and Settings\Tim\My Documents\Arduino\libraries\nvlcd\nvlcd.cpp:303: warning: 'VAClearPW' may be used uninitialized in this function
C:\Documents and Settings\Tim\My Documents\Arduino\libraries\nvlcd\nvlcd.cpp:302: warning: 'DriveV' may be used uninitialized in this function
C:\Documents and Settings\Tim\My Documents\Arduino\libraries\nvlcd\nvlcd.cpp:301: warning: 'DrivePW' may be used uninitialized in this function

That was the sort of thing I was talking about, but you say you aren't using this?

Yeah, those are the ones I was talking about. This is the function that actually drives images onto the display; it's not called by any of the testcases though. This way the testcases will run without any display attached. These constants are pulled out of a temperature-compensation table; in the current (ugly, to be cleaned up once it's actually working) code, they could be left uninitialized if a very out-of-range temperature is specified.

In short- they're not it :frowning:

Something funky is going on in that constructor, but I can't for the life of me imagine what.

Here, a byte value cannot be -1 as it is unsigned.

NVLCD::NVLCD(byte _cs_pin, byte _busy_pin, byte _c_d_pin, byte _reset_pin, byte _pwr_pin, byte _gnd_pin, byte _sck_pin, byte _mosi_pin)

...

    if(sck_pin > 127 || mosi_pin > 127) // if user enters nonvalid or '-1' value

You have two constructors, but appear to be using the one that takes six arguments:

    NVLCD lcd=NVLCD(K_CS, K_BUSY, K_D_C, K_RESET, K_SCLK, K_MOSI);

The constructor arguments do not appear to agree with what you are passing in:

NVLCD::NVLCD(byte _cs_pin, byte _busy_pin, byte _c_d_pin, byte _reset_pin, byte _pwr_pin, byte _gnd_pin)

You are passing in CS, busy, CD, reset, SCLK, MOSI. But the constructor is expecting other stuff like power-pin and ground-pin.