There are some tools for this, purif, bounds checker but in my opinion they tend to present you with to much information. (Also they can be expensive)
This is what I would do once you have Identified the crash point:
Note the address at which the crash occurred, view the memory at this address looking to see if it give any clues as to where the overrun occured
Then work backward from that point, if your debugger will rewind move back in the code watching the memory for the statement that changes it (goes red).
Sometimes if you restart your application and debug it, it will be at the same address as last time, if so you can watch the memory in the memory window as you step through the code.
If you can't do the above, comment out code before the crash point until it stops crashing this will help you identify the cause line(s) this techneque also works in release builds.
Things that help avoid this problem
always use the mem copy functiosn the allow a size limit to be specified
strncpy, memncpy for example. common causes are memcpy, strcat strcpy copying more bytes than in a buffer.
I've also seen this problem due to poor understanding of pointers e.g.
take a function definition like
void SetValue(int* pValToSet) { *pValToSet = 100; }
This function just set an int to 100, but sometime it is called incorrectly like this
int* pIntToSet;
SetValue(pIntToSet);
The pointer is no set to anything and will contain a random number in release mode that may be an address
should be
int IntToSet = 0;
SetValue(&IntToSet);
Other tips:
Always initialise pointers to nullptr
When dealing with strings set memory holding the string to 0 (null) e.g.
char myString[255];
memset(myString, 0, sizeof(myString));
There are many more examples, but sadly I don't have time to turn this into an essay