26 Sep, 2009, Koron wrote in the 1st comment:
Votes: 0
I'm pretty comfortable with C, but this whole C++ thing is a bit of a learning experience. Any time I want to analyze a crash in gdb, I find its output slightly less than helpful. For example, we have this:
<std::_List_base<BLAH_DATA*, std::allocator<BLAH_DATA*> >> = {
_M_impl = {
<std::allocator<std::_List_node<BLAH_DATA*> >> = {
<__gnu_cxx::new_allocator<std::_List_node<BLAH_DATA*> >> = {<No data fields>}, <No data fields>},
members of std::_List_base<BLAH_DATA*, std::allocator<BLAH_DATA*> >::_List_impl:
_M_node = {
_M_next = 0x8353b38,
_M_prev = 0x8353b38
}
}
}, <No data fields>}

In my google searches, I came upon a really sweet page that would be all kinds of helpful if I weren't referencing a list of mud structs. I know there must be a way to turn this into comprehensible information, but as yet I have not had any success finding it.

Any advice would be super sweet. :smile:
26 Sep, 2009, David Haley wrote in the 2nd comment:
Votes: 0
Generally I just mentally filter out that sort of stuff. It's saying you have a list of BLAH_DATA pointers, basically. Templates make it somewhat hard for the debugger to know what to do.
26 Sep, 2009, Koron wrote in the 3rd comment:
Votes: 0
So far that's what I've done too, but I'm really interested in accessing the full information on what's in this list. :(
26 Sep, 2009, Koron wrote in the 4th comment:
Votes: 0
Wow, I think I just figured it out. Way to not try quite hard enough, me!

Edit: Though I guess I have no way of knowing if I'm doing it the best way, so suggestions are welcome. I'm using the .gdbinit file from here to use plist to grab an entry. It displays it terribly wrong, but I can then do this:
print *(BLAH_DATA *)$#

Where # depends on what plist throws at me. Heh.
26 Sep, 2009, David Haley wrote in the 5th comment:
Votes: 0
You mean the contents of the list? If the list is called foo, try foo._M_impl._M_node, which is the first node in the linked list.
That said, I find it somewhat difficult to inspect lists like this at debugging time, because the implementation is squirreled away and you can't always do things like call the .begin() method to start normal iteration.
26 Sep, 2009, Koron wrote in the 6th comment:
Votes: 0
(Note that I edited the above post to add more info.)

The lack of gdb integration has been my only real complaint about switching to C++. I just about live on gdb, so being less able to use it really hurts, especially since I don't know the nuances of plusplus yet.
26 Sep, 2009, JohnnyStarr wrote in the 7th comment:
Votes: 0
Koron said:
The lack of gdb integration has been my only real complaint about switching to C++.

What do you mean by this exactly? Are you saying that you have switched a mud project? Or you have switched your
learning pursuits to C++ instead of C?
26 Sep, 2009, Koron wrote in the 8th comment:
Votes: 0
We recently converted our mud from C to C++. Our std:: variables are terribly difficult to examine in gdb. I'm used to being able to swim through a linked list by printing X->first_y and $->next, so the fact that it takes twice as many steps (and four times as much useless info) to do the same thing now is frustrating. I'm perfectly willing to accept the idea that I'm not "getting" something and that it's a lot easier than I'm making it out to be. Actually, I'm really hoping that's the case. There are a lot of people far smarter than I who work with/on gdb and C++ and this thread is the result of my hope that one of them would take notice and say, "No you silly Koron, it's so easy! The command to do what you want is…"

I really like the .empty(), .begin() etc. options from an implementation standpoint, but my appreciation of these things wanes when debugging. :grinning:
26 Sep, 2009, David Haley wrote in the 9th comment:
Votes: 0
It's not so much integration with C++ in general, it's the STL in particular. I agree that debugging STL containers is a little hairy. That said, in practice, I have almost never needed to inspect the contents of an STL container; not really sure why that is, I just haven't needed to.
26 Sep, 2009, elanthis wrote in the 10th comment:
Votes: 0
Try the Project Archer branch of GDB. It's got a number of extensions to make dealing with C++ easier in GDB. Unfortunately, debugging C++ with the GNU tools is just a pain. C++ is a second-class citizen in the GNU world because the GNU idiots still think that LISP is the One True Way to program and they begrudgingly accept C into their folds because they realized that you can't write an OS or low-level tools in LISP. C++ support only even exists in a usable state in GCC because the actual users and developers threw a fit, forked GCC, and almost killed off the project before RMS and the GCC devs caved in. GCC used to stand for "GNU C Compiler" and now stands for "GNU Compiler Collection." While GCC has quite excellent support for compiling C++, it still has horrific support for debugging C++, which in turn means that even if GDB had the best C++ debugging support in the world (which it doesn't and probably never will), the debuginfo produced by GCC would still make debugging C++ code a pain in the ass. If you can, try a better compiler, like the Intel Compiler Suite, which also includes a better quality debugger with proper C++ support. Hopefully either GNU gets its shit together soon or Apple's Clang C++ support will mature enough to make GCC irrelevant.

Converting C code to C++ has the problem that a ton of mysterious issues show up that look like C++ junk when the real problem is C junk screwing up the C++ stuff. For example, people converting C projects have a habit of putting C++ objects into C structs, allocating the structs with malloc, never letting the C++ objects get constructed properly, and then getting mystified when crashes occur.

If you are converting to C++, the very first thing you should do is axe every single use of malloc(), free(), realloc(), strdup(), and other C-style memory allocations, and use the equivalent C++ mechanisms (there is no C++ replacement for realloc(), but then you really don't need things like realloc() in well-written application code when you have std::vector and other STL containers). That in turn may require converting a lot of C strings into C++ strings. There are acceptable places for those functions in C++ apps, but they're very rare, and the overwhelming odds are that none of your uses qualify as good excuses. It can be a fair bit of work, but in the long run it helps a lot.

The second most likely cause for people thinking there's something wrong with an STL containers is unsafe use of iterators. If you use STL container iterators, and inside the loop you are modifying the container (which might be happening deep inside the call tree of a function you're calling in the loop!), then the iterators can become invalid and will cause a crash if used! Take a std::vector and its iterators. The way a vector grows is much like using realloc() on a C array. When the array grows, C++ tries to get more memory. It might not be able to just grow the currently used block of memory, so it allocates a new one and copies the data over. If you had a pointer into that C array, the pointer is now pointing at garbage. For something like a vector, using the integer index is safer in that it doesn't become invalid, but it is still incorrect to use it (and this is just as true for C!) if you are possibly modifying the order of elements in the vector (or C array). If your index is pointing to item 3, and inside the loop you push a new element to the front of the vector/array, the next loop iteration will access item 4… which is actually the same item as item 3 in the previous loop iteration, so you process the same element twice. Likewise, if you remove an item, you run the risk of skipping over an item in the next loop iteration. Using an integer index with a vector/array removes the chance of a crash from dereferencing a pointer/iterator, but it's still an inherently broken concept. Most programmers just don't realize it because all they notice is "it don't crash kewl lol" and go on with their merry lives.

Moral of the story is that you cannot at all safely allow a container – be it a C++ STL container or a C array or a C linked list or anything else – to be modified while iterating over the container without taking very explicit steps to ensure that you keep container modifications and iterators synchronized. This is often a major pain in the ass. You are often better off just making sure you never modify a container directly, but instead use delayed updates. E.g., if you have a Room object with a list of Mob objects, and you need to add a new Mob to a Room, do not just add it to the Room. Instead, create the Mob object in a pending state and put it in a pending addition container. In your main game loop (which is guaranteed not to be running inside of any other loops), pop pending Mobs off the pending mob container and add them to their target Rooms.

You want to do the same thing when you delete any game object, unless you are using Boost/TR1/C++0x shared pointers everywhere. When a Mob is destroyed, do not just remove it from the Room and delete it. Somewhere up the call chain might be a loop over that Room's mob list or there might be some function that's holding a pointer to the Mob. Set a deleted flag on the Mob and add it to a pending delete container. Then, back in the main game loop, process the pending deletes. This guarantees that things do not break.

Programmers more familiar with memory-managed languages tend to prefer the smart pointer approach. Both C and C++ professional game programmers generally prefer the delayed deallocation and insertion approach because (on a large scale) it's a simpler design, guaranteed to be safe and correct, and it also helps solve a number of other game-related issues. (Like infinite event delivery loops locking up the game – better to make sure the loop can't lock anything up and thus make it possible to debug via a debug console or debug log more easily.)
27 Sep, 2009, Koron wrote in the 11th comment:
Votes: 0
Wow, thanks for the comprehensive reply, elanthis! I really like the delayed deallocation/insertion suggestion, though I've only experienced problems with the former category (ie, trying to affect a mob after its deletion).
27 Sep, 2009, Tyche wrote in the 12th comment:
Votes: 0
I'll admit that I do most of my C++ debugging for Unix using Borland's TurboDebugger on Windows. I almost always am writing cross platform code anyway.
0.0/12