FAQ Search
Memberlist Usergroups
Profile
  Forum Statistics Register
 Log in to check your private messages
Log in to check your private messages
Moonpod Homepage Starscape Information Mr. Robot Information Free Game Downloads Starscape Highscore Table
Exceptions
Post new topic   Reply to topic    Discussion Pod Forum Index -> Independent Game Development View previous topic :: View next topic  
 Author
Message
Weeble
Starscape Jedi
Starscape Jedi


Joined: 25 Apr 2003
Posts: 1143
Location: Glasgow, Scotland



PostPosted: Fri Apr 21, 2006 4:59 pm    Post subject: Exceptions Reply with quote

What's the opinion of C++ exceptions over at Moonpod? Useful? Costly? Dangerous?
Back to top
View user's profile Visit poster's website MSN Messenger
Poo Bear
Pod Team
Pod Team


Joined: 14 Oct 2002
Posts: 4121
Location: Sheffield, UK



PostPosted: Sat Apr 22, 2006 10:40 am    Post subject: Reply with quote

With VC6/7 there was too great a performance overhead, but with VC8 maybe that is no longer true?

In terms of validating code, i've got used to using assert and verify, I use my own version so it's also platform independent. I know it isn't the same thing as exceptions, but in use you get similar benefits i.e. catching code that has entered an unexpected state. You just have to get into the habit of using them everywhere and thinking about what the expected state of the code should be and the boundary conditions.

--these disappear when I make a release build
MP_ASSERT(x)
MP_MESS_ASSERT( "your message", x )

--these stay even in release builds, useful if you are letting people write add-ons or scripts or you need to test the code running at full speed.
MP_VERIFY(x)
MP_MESS_VERIFY( "your message", x )

--a useful extra tool lets you make the messages dynamic
MP_MESS_VERIFY( MP_CAT( "Error: x=", m_nameStr, " in input" ), 0 )

Functionality wise, when they fire up you get the message, file and line number plus the call stack. You also get the option there and then to ignore the assert this time, break into the debugger or ignore the assert permanently, very useful.

I also override new and delete globally so I can monitor if, when and where things get deleted. There is an app called boundschecker that attempts to do the same thing automatically, but i've found it doesn't always catch everything. The best option is to use both.
Back to top
View user's profile Visit poster's website
Weeble
Starscape Jedi
Starscape Jedi


Joined: 25 Apr 2003
Posts: 1143
Location: Glasgow, Scotland



PostPosted: Sat Apr 22, 2006 12:12 pm    Post subject: Reply with quote

How do you implement MP_CAT without leaking?

I saw a cute way of putting messages into regular assertions:

assert(count<=5 && "There should never be more than 5 doohickeys at once.");

I'm still in two minds about messages in assertions. I often find it easier to express and understand the condition than the message, especially if it's in the middle of an algorithm. It can be messy trying to explain why the condition should hold. When I do have messages I find I prefer "Such and such should have happened" to "Such and such is wrong". The former seems to make it easier to read the code - it's telling you the way things are supposed to be. The latter I find jarring, somehow. I guess I feel that an assertion should be primarily a positive statement about the current state of things, and including an error message in there seems to cloud its meaning for me.

What do you add to the overridden new and delete? Do you store a stack trace with the allocated block? Or do you add parameters to new? Does it matter if DirectX or STL stuff uses them? Do you even use any of the STL?
Back to top
View user's profile Visit poster's website MSN Messenger
Poo Bear
Pod Team
Pod Team


Joined: 14 Oct 2002
Posts: 4121
Location: Sheffield, UK



PostPosted: Mon Apr 24, 2006 7:48 am    Post subject: Reply with quote

Weeble wrote:
How do you implement MP_CAT without leaking?


Have a large static string array and strcat all the seperate bits of text into it before displaying.


Weeble wrote:
What do you add to the overridden new and delete? Do you store a stack trace with the allocated block? Or do you add parameters to new?


I store the file, line number, type of object and amount of memory. An alarm goes off if I try to free something that doesn't appear to have been new'd properly or if I'm exiting the game without deleting everything. It also lets me check what is being allocated and when, which can be useful i.e. you can dump out a list of what is currently allocated and what has already been deleted.


Weeble wrote:
Does it matter if DirectX or STL stuff uses them? Do you even use any of the STL?


DX and Stl do their own thing I think. They have their own built in memory checking though. In DX you just need to switch it into debug mode and turn all debuging options on, then it will tell you if something hasn't been released properly. With the stl, if you're running in debug mode it will be very slow indeed which I think is related to the extra checks it is doing. Every time i've done anything wrong with a container i've got a built in assert (so far). The problem with the stl is every compiler's implementation is different and it might not even exist on some platforms. It's so valuable i've been using it anyway though, it saves time and reduces bugs.
Back to top
View user's profile Visit poster's website
Weeble
Starscape Jedi
Starscape Jedi


Joined: 25 Apr 2003
Posts: 1143
Location: Glasgow, Scotland



PostPosted: Mon Apr 24, 2006 9:37 am    Post subject: Reply with quote

I think the reason the STL is so slow in debug is that it gets heavily inlined by the optimizer, and it is designed to take advantage of this. I've found that stepping through operations on a std::vector involves a lot of calls in debug, but barely any in release. std::auto_ptr involves calls for every dereference, but these disappear completely under optimisation and it looks just like a normal pointer.

I'm interested in the overridden new and delete, because something I've had to work with involved overridden new and delete that needed you to call special macros, both before every usage of new, and before and after every call to a library function that might call new itself. While this ensures there is plenty of information for analysing allocation and deallocation patterns, it also often makes it impossible to use initialiser lists or have foreign member objects that allocate their own memory in a constructor, because there's nowhere to fit in the macro calls. I'm sure there must be a better way to achieve the same effect. (I've also discovered that it isn't very thread-friendly, but that's beside the point right now.)
Back to top
View user's profile Visit poster's website MSN Messenger
Poo Bear
Pod Team
Pod Team


Joined: 14 Oct 2002
Posts: 4121
Location: Sheffield, UK



PostPosted: Mon Apr 24, 2006 10:16 am    Post subject: Reply with quote

Oh sorry, my language has mislead you, I don't actually override new and delete themselves that causes too many problems with other libraries I meant I use different calls and simply watch what they are doing. Sorry, I confused matters there with bad language choice. I'm only mapping my own memory usage patterns, hence the suggestion you also use something like boundschecker as well (you might even be able to just rely on it completely, I haven't checked the latest version so it may have been improved enough).

I do this when I new something:

#define MPNEW(objClass) (objClass*)MPDebugNew(new objClass, #objClass, sizeof(objClass), __FILE__, __LINE__)
...
...
#define MPNEWP10(objClass, parm1, parm2, parm3, parm4, parm5, parm6, parm7, parm8, parm9, parm10) (objClass*)MPDebugNew(new objClass(parm1, parm2, parm3, parm4, parm5, parm6, parm7, parm8, parm9, parm10), #objClass, sizeof(objClass), __FILE__, __LINE__)

#define MPNEWARRAY(objClass,num) (objClass*)MPDebugNew(new objClass[num], #objClass, sizeof(objClass)*num, __FILE__, __LINE__)


and match it with this on delete:

#define MPDELETE(x) { delete x; MPDebugDelete(x, __FILE__, __LINE__); }

#define MPDELETEARRAY(x) { delete [] x; MPDebugDelete(x, __FILE__, __LINE__); }

So its just calling new/delete as normal, but letting me keep an eye on it.
Back to top
View user's profile Visit poster's website
SethP



Joined: 24 Apr 2006
Posts: 302
Location: Connecticut, USA



PostPosted: Mon Apr 24, 2006 10:33 pm    Post subject: Reply with quote

Poo Bear wrote:

#define MPNEW(objClass) (objClass*)MPDebugNew(new objClass, #objClass, sizeof(objClass), __FILE__, __LINE__)
...
#define MPDELETE(x) { delete x; MPDebugDelete(x, __FILE__, __LINE__); }


I'm confused as to how the MPDebugNew/Delete macros work. If you wouldn't mind taking the time, I'd appreciate a little more info on them. Mainly, what does the statement "#objClass" do, how do you keep track of which object was allocated where, and how do you do a dump of all the news and deletes?

Sorry if I asked you to reveal any trade secrets or anything that would take too long to answer Smile. Thanks in advance for your time.
Back to top
View user's profile MSN Messenger
Weeble
Starscape Jedi
Starscape Jedi


Joined: 25 Apr 2003
Posts: 1143
Location: Glasgow, Scotland



PostPosted: Tue Apr 25, 2006 12:41 am    Post subject: Reply with quote

I'm pretty sure using # in a macro makes a string literal out of the parameter. So
Code:
MPNEW(Sprite);
on line 123 of the file SpriteManager.cpp would expand to:
Code:
(Sprite*)MPDebugNew(new Sprite, "Sprite", sizeof(Sprite), "SpriteManager.cpp", 123);


I expect that MPDebugNew then allocates a block of memory big enough for the class being allocated, plus the size of the extra book-keeping information passed into it by the macro, stores that stuff in there, does a placement-new on the rest and returns a pointer to the start of the object. It might also be able to log the allocation to a file.

It's late and I need to sleep. You might want to google "placement new".
Back to top
View user's profile Visit poster's website MSN Messenger
SethP



Joined: 24 Apr 2006
Posts: 302
Location: Connecticut, USA



PostPosted: Tue Apr 25, 2006 1:36 am    Post subject: Reply with quote

Weeble wrote:
I'm pretty sure using # in a macro makes a string literal out of the parameter. So
Code:
MPNEW(Sprite);
on line 123 of the file SpriteManager.cpp would expand to:
Code:
(Sprite*)MPDebugNew(new Sprite, "Sprite", sizeof(Sprite), "SpriteManager.cpp", 123);


I expect that MPDebugNew then allocates a block of memory big enough for the class being allocated, plus the size of the extra book-keeping information passed into it by the macro, stores that stuff in there, does a placement-new on the rest and returns a pointer to the start of the object. It might also be able to log the allocation to a file.

It's late and I need to sleep. You might want to google "placement new".


Ah, so a snippet might look something like:

Code:

int size = sizeof(Sprite);
size += sizeof(file); //I forgot how to determine the size of a string
size += sizeof(line); //whatever type of variable __LINE__ evaluates out to be

int* buffer = new int[size / sizeof(int)];

Sprite* ptr = new (buffer) Sprite();

//code for storing away __FILE__ and __LINE__


Does that look halfway decent (assuming I figured out a way to accurately calculate the sizes of __FILE__ and __LINE__), or did I completely misunderstand what you were saying?

Thanks for your help Smile
Back to top
View user's profile MSN Messenger
Poo Bear
Pod Team
Pod Team


Joined: 14 Oct 2002
Posts: 4121
Location: Sheffield, UK



PostPosted: Tue Apr 25, 2006 8:23 am    Post subject: Reply with quote

Weeble has it right, as you're interested here is some more code. It isn't really very portable so it wont be much practical use but might help make things clearer.

Calling this creates the object we are interested in and then calls the tracking code. Notice the "new" in the first parameter.

Code:

#define MPNEW(objClass) (objClass*)MPDebugNew(new objClass, #objClass, sizeof(objClass), __FILE__, __LINE__)


This then calls into a global memory tracking object that does the book keeping.
Code:

extern "C" void *MPDebugNew( void *pObj, const char *type, size_t size, const char *file, unsigned int line )
{
   MPMESSASSERT( MPAssertCat("Memory allocation failed, ", file, MPAssertInt(line)), pObj );
   g_memoryTracker.Allocate( pObj, type, size, file, line );
   return pObj;
}


Here is the allocation code, the hash just lets you look up info later a little faster, not important. The matching delete call would remove the entry. So you now have a record of everything currently allocated.
Code:

   void Allocate( const void *pObj, const char *type, size_t size, const char *file, unsigned int line )
   {
      CAllocation *pAlloc = new CAllocation;
      MPASSERT(pAlloc);

      strcpy(pAlloc->type, type);
      pAlloc->pMem = pObj;
      pAlloc->size = size;
      pAlloc->file = file;
      pAlloc->line = line;

      t_uint32 hashListIdx = GenerateHashKeyFromAddress(pObj);

      pAlloc->m_pPrevious=NULL;
      pAlloc->m_pNext = m_hashLists[hashListIdx];
      if ( m_hashLists[hashListIdx] )
         m_hashLists[hashListIdx]->m_pPrevious = pAlloc;
      m_hashLists[hashListIdx] = pAlloc;
   }
Back to top
View user's profile Visit poster's website
Weeble
Starscape Jedi
Starscape Jedi


Joined: 25 Apr 2003
Posts: 1143
Location: Glasgow, Scotland



PostPosted: Tue Apr 25, 2006 9:19 am    Post subject: Reply with quote

Ah, yeah, the new was staring me in the face and somehow I forgot about it. I've seen the allocating extra space and then partitioning it manually strategy more than once. I think people like it because it helps with debugging when looking at raw memory.
Back to top
View user's profile Visit poster's website MSN Messenger
Poo Bear
Pod Team
Pod Team


Joined: 14 Oct 2002
Posts: 4121
Location: Sheffield, UK



PostPosted: Tue Apr 25, 2006 9:43 am    Post subject: Reply with quote

This call can be useful if you are using VisualC++

_CrtCheckMemory()

it returns false if the heap is invalid, it inspects every memory block so it is slow, but handy if something appears to be getting corrupt and you can't track it down.
Back to top
View user's profile Visit poster's website
SethP



Joined: 24 Apr 2006
Posts: 302
Location: Connecticut, USA



PostPosted: Wed Apr 26, 2006 2:53 am    Post subject: Reply with quote

Thank you very much for your reply Poo Bear. That code is very interesting Smile
Back to top
View user's profile MSN Messenger
Poo Bear
Pod Team
Pod Team


Joined: 14 Oct 2002
Posts: 4121
Location: Sheffield, UK



PostPosted: Tue May 16, 2006 8:41 am    Post subject: Reply with quote

http://www.gamearchitect.net/Articles/ExceptionsAndErrorCodes.html

This article looks at exceptions in detail and concludes there is a not insignificant peformance and memory overhead. So it still isn't quite time to switch if you're making something performance critical.
Back to top
View user's profile Visit poster's website
Weeble
Starscape Jedi
Starscape Jedi


Joined: 25 Apr 2003
Posts: 1143
Location: Glasgow, Scotland



PostPosted: Tue May 16, 2006 12:20 pm    Post subject: Reply with quote

It annoys me that I can't use exception-like syntax with error-code-like behaviour, particularly when every function involved declares exactly which exceptions it throws. If I can type it out as error-codes with the messiness they incur, why can't the compiler do that work for me? Sure, it doesn't work if a function can throw an exception that its direct caller doesn't know about, but in a great many useful circumstances we can know all about all of the exceptions involved.
Back to top
View user's profile Visit poster's website MSN Messenger
Display posts from previous:   
Post new topic   Reply to topic    Discussion Pod Forum Index -> Independent Game Development All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum


Powered by phpBB © 2001, 2005 phpBB Group