User:Pkmnfrk/Mystery SIGTRAPs in FreeBasic/redim foo(ubound(foo) + 1)

From OHRRPGCE-Wiki
Jump to navigation Jump to search

The error[edit]

The first clue you have that you are experiencing this problem is a tell-tale warning message, hinting at stack corruption:

D:\ohrrpgce>gdb crash1
GNU gdb 5.2.1
Copyright 2002 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i686-pc-mingw32"...
(gdb) r
Starting program: D:\ohrrpgce/crash1.exe
warning: HEAP[crash1.exe]:
warning: Invalid Address specified to RtlFreeHeap( 00320000, 0012FF80 )

Of course, your executable name will vary, as will the addresses in the message.

Debugging[edit]

Regardless, let's pull up a backtrace, shall we?

(gdb) bt
#0  0x7c901231 in _C__prg_code_bas_FreeBASIC_lib_win32_def____libmsvcrt_dll_a_iname ()
#1  0x7c96c943 in _C__prg_code_bas_FreeBASIC_lib_win32_def____libmsvcrt_dll_a_iname ()
#2  0x7c96cd80 in _C__prg_code_bas_FreeBASIC_lib_win32_def____libmsvcrt_dll_a_iname ()
#3  0x7c96df66 in _C__prg_code_bas_FreeBASIC_lib_win32_def____libmsvcrt_dll_a_iname ()
#4  0x7c94a5d0 in _C__prg_code_bas_FreeBASIC_lib_win32_def____libmsvcrt_dll_a_iname ()
#5  0x7c9268ad in _C__prg_code_bas_FreeBASIC_lib_win32_def____libmsvcrt_dll_a_iname ()
#6  0x77c2c2de in _C__prg_code_bas_FreeBASIC_lib_win32_def____libmsvcrt_dll_a_iname ()
#7  0x00401582 in hRedim ()
#8  0x0040161c in fb_ArrayRedimEx ()
#9  0x004012f5 in DOSOMETHING (FOO=
      {data = 0x12ff80, ptr = 0x12ff80, size = 12, elen = 4, dims = 1, dim1_elemns = 3, dim1_lbound = 0, dim1_ubound = 2}) at crash1.bas:3
#10 0x004013b6 in main (__FB_ARGC__=1, __FB_ARGV__=0x323da8) at crash1.bas:11

Oh my. The SIGTRAP was issued from deep within the runtime library. We're 9 frame pointers removed from the error! Surely it's not us... or, is it? Since we don't have the source for the runtime, let's backtrack to the last bit of code we executed:

(gdb) f 9
#9  0x004012f5 in DOSOMETHING (FOO=
      {data = 0x12ff80, ptr = 0x12ff80, size = 12, elen = 4, dims = 1, dim1_elemns = 3, dim1_lbound = 0, dim1_ubound = 2}) at crash1.bas:3
3               redim foo(ubound(foo) + 1)

Well, the last thing we did was to resize an array. This is a valid, well defined action. According to the bound info passed with the parameter, we're not overflowing anything. So, is this our culprit?

The answer is yes. Why? Let's dig deeper. Let's pull up the previous stack frame:

(gdb) f 10
#10 0x004013b6 in main (__FB_ARGC__=1, __FB_ARGV__=0x323da8) at crash1.bas:11
11      dosomething temp()

The cause[edit]

Ah, okay. We're passing the temp() array to DoSomething(), which resizes it. Again, still valid. So, why doesn't it like the array? Let's expand our view a bit, maybe find where temp() is declared.

(gdb) list
6               next
7       end sub
8
9       dim as integer temp(2)
10
11      dosomething temp()
12      for i as integer = lbound(temp) to ubound(temp)
13              print i
14      next

Well, already, I can see what the problem is. Can you? That's alright, I'll wait.

...

Done yet? No? Look closely at the line I highlighted:

9       dim as integer temp(2)

That's right, temp() is a static array. Rediming it directly is invalid, but when we pass it to DoSomething, it can't tell that it's static, and so allows the Redim. The problem is that temp() is located on the stack, which is not nearly as flexible as the heap (read: where dynamic arrays are stored). And, Redim tries to free the old memory (on the stack) as heap memory. Hilarity ensues.

By that, I mean that the operation will fail, and nothing will change. Further writes to the "new" array will likely scribble all over other important stack variables, and possibly crash (if you're lucky).

Anyway, how do you fix it? You have two options:

Resolution: Turn the static array into a dynamic array[edit]

Just change this:

dim as integer temp(2)

in to this:

redim as integer temp(2)

Two extra characters, no error message! It's win-win! This should be your default solution.

However, in certain cases, you cannot do this (for totally arbitrary reasons), eg an array in a UDT:

Type MyType
  myarray() as integer 'error 11: Expected constant, found ')' in 'myarray() as integer'
End Type

So, you might...

Resolution: Remove the Redim[edit]

If possible, rework your code so that the Redim is unnecessary. You might make the static arrays big enough that you need not resize them. Or... something. Really, it's hard to come up with a general suggestion for this case, as it depends on what your code does.

If this is not satisfactory (eg, you're working with arbitrarily large sets of data), then there is a third option, which is not for the faint of heart...

Resolution: Manage the memory yourself[edit]

If you need to be able to embed an array in a UDT, but need for it to grow, then you might consider managing the memory yourself. It's not as complicated as it seems, but it has to be done carefully, lest you introduce new, more critical bugs.

To allocate such an array, simply replace this:

dim as integer temp(2)

with this:

dim as integer ptr temp = callocate(3, sizeof(integer))

Now, temp is a pointer to a block of memory which you can play around with. We explicitly asked for 3 elements worth (0 to 2), and we can access them similarly to a regular array:

temp[1] = 123
print temp[0]

Note the square brackets instead of round ones.

If we need to resize it, we can use the Reallocate command:

dim newmem as integer ptr
newmem = Reallocate(temp, sizeof(integer) * 10)
if newmem <> 0 then
 temp = newmem
end if

This looks more complicated, but isn't really. Reallocate may fail if there isn't enough memory. In this case, it will return 0. We need to check for that. If we get a 0, then our old array is still valid. But, if we get something that isn't a 0, then it's valid, and our old buffer is not.

The last thing we need to know is that when you're done with the array, you need to delete it manually (it won't go away magically like normal arrays). It is easy:

Deallocate(temp)

That's all! temp is no longer valid, so don't try to use it!

Summary[edit]

Long story short, resizing a static array is a big no-no. If you can, just make the array dynamic. If you can't do that, pre-grow the array. If that's not possible, then you probably need to manage your own memory.