 |
Author |
|
 |
|
Weeble Starscape Jedi


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

|
Posted: Fri Apr 21, 2006 4:59 pm Post subject: Exceptions |
|
|
| What's the opinion of C++ exceptions over at Moonpod? Useful? Costly? Dangerous? |
|
|
|
Back to top |
|
|
 |
|
Poo Bear Pod Team


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

|
Posted: Sat Apr 22, 2006 10:40 am Post subject: |
|
|
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 |
|
|
 |
|
Weeble Starscape Jedi


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

|
Posted: Sat Apr 22, 2006 12:12 pm Post subject: |
|
|
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 |
|
|
 |
|
Poo Bear Pod Team


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

|
Posted: Mon Apr 24, 2006 7:48 am Post subject: |
|
|
| 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 |
|
|
 |
|
Weeble Starscape Jedi


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

|
Posted: Mon Apr 24, 2006 9:37 am Post subject: |
|
|
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 |
|
|
 |
|
Poo Bear Pod Team


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

|
Posted: Mon Apr 24, 2006 10:16 am Post subject: |
|
|
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 |
|
|
 |
|
SethP
Joined: 24 Apr 2006 Posts: 303 Location: Connecticut, USA

|
Posted: Mon Apr 24, 2006 10:33 pm Post subject: |
|
|
| 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 . Thanks in advance for your time. |
|
|
|
Back to top |
|
|
 |
|
Weeble Starscape Jedi


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

|
Posted: Tue Apr 25, 2006 12:41 am Post subject: |
|
|
I'm pretty sure using # in a macro makes a string literal out of the parameter. So 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 |
|
|
 |
|
SethP
Joined: 24 Apr 2006 Posts: 303 Location: Connecticut, USA

|
Posted: Tue Apr 25, 2006 1:36 am Post subject: |
|
|
| Weeble wrote: | I'm pretty sure using # in a macro makes a string literal out of the parameter. So 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  |
|
|
|
Back to top |
|
|
 |
|
Poo Bear Pod Team


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

|
Posted: Tue Apr 25, 2006 8:23 am Post subject: |
|
|
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 |
|
|
 |
|
Weeble Starscape Jedi


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

|
Posted: Tue Apr 25, 2006 9:19 am Post subject: |
|
|
| 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 |
|
|
 |
|
Poo Bear Pod Team


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

|
Posted: Tue Apr 25, 2006 9:43 am Post subject: |
|
|
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 |
|
|
 |
|
SethP
Joined: 24 Apr 2006 Posts: 303 Location: Connecticut, USA

|
Posted: Wed Apr 26, 2006 2:53 am Post subject: |
|
|
Thank you very much for your reply Poo Bear. That code is very interesting  |
|
|
|
Back to top |
|
|
 |
|
Poo Bear Pod Team


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

|
|
|
|
Back to top |
|
|
 |
|
Weeble Starscape Jedi


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

|
Posted: Tue May 16, 2006 12:20 pm Post subject: |
|
|
| 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 |
|
|
 |
|
|
|
|
|
|