If you write native code long enough, EXCEPTION_ACCESS_VIOLATION becomes an old, unwelcome friend. It's the most common way a C++ program dies, it shows up as the cryptic code 0xC0000005, and on a good day it points you straight at a null pointer. On a bad day it points at memory that was fine right up until it wasn't.
Here's what an access violation actually is, how to read one, and how to stop chasing it around your codebase by hand.
What an access violation is
Every process gets a map of memory it's allowed to touch. An access violation is what happens when your code tries to read or write somewhere outside that map: an address that was never valid, or one that used to be valid and isn't anymore. The CPU catches the illegal access, raises a hardware exception, and Windows terminates the process. On Linux and macOS the same failure shows up as a segmentation fault (SIGSEGV); it's the same underlying sin with a different name.
Windows reports it as EXCEPTION_ACCESS_VIOLATION with status code 0xC0000005, and the crash usually tells you two useful things: whether you were reading or writing, and the address you tried to touch. Those two facts narrow the cause faster than anything else.
Reading the address: the single most useful trick
The address in an access violation is a clue, and one pattern is worth committing to memory.
If the faulting address is very small, something near 0x00000000 or a low offset like 0x0000001C, you almost certainly dereferenced a null pointer. Here's why the offset isn't always exactly zero: when you call a method on a null object and that method reaches for a member variable, the program adds the member's offset to a base address of zero. So a null pointer plus a field sitting 28 bytes into the struct gives you a fault at 0x1C. A tiny address means null, full stop, and the exact value hints at which field.
A large or random-looking address is a different animal. That usually means a wild or dangling pointer: memory that was valid once, got freed or fell out of scope, and is now being used anyway. These are nastier, because the pointer holds a plausible-looking value that points at garbage.
The usual suspects
Access violations come from a short list of repeat offenders:
A null pointer dereference, the most common by far. You called a method or read a field on a pointer that was null. (See the small-address tell above.)
A dangling pointer, where you're using memory after it was freed or after the object went out of scope. Use-after-free is this one's most dangerous form.
An uninitialized pointer that was never set to anything and holds whatever garbage was on the stack.
An out-of-bounds access, walking off the end of an array or buffer into memory you don't own.
A stack/heap corruption elsewhere that quietly trashed a pointer, so the crash happens nowhere near the actual bug. This is the one that eats afternoons.
The fix is almost always disciplined pointer hygiene: initialize pointers, null them after freeing, check before dereferencing, and let smart pointers and bounds-checked containers do the work your memory deserves. Or rather, the work your memory needs, since none of us really deserve anything good after the pointer code we wrote at 2am.
Why a crash reporter beats your debugger here
The catch with access violations is that the crashes that matter happen on your users' machines, not yours. The wild-pointer crash that fires once in 500 sessions is exactly the one you'll never reproduce locally, and a debugger is no help for a crash you can't make happen.
This is where a crash reporter built for native code earns its keep. When the access violation fires in the field, BugSplat captures the minidump and symbolicates it against the symbols you uploaded at build time, so you get the real function names and line numbers instead of hex. For Windows native crashes it also surfaces a table of local variables and function arguments at the crash site, which for an access violation is often the whole answer: you can see which pointer was null without reproducing anything. That's the difference between "something was null in this function" and "inventory was null on line 214."
It also groups every instance of the same access violation together, so 4,000 identical crashes read as one defect to fix rather than 4,000 things to triage.
Logs and crash data work better together
One thing worth saying clearly, because it gets framed as a versus when it shouldn't: crash reporting doesn't replace your logging, it gives your logs a home. An access violation with a clean stack trace tells you where it died. Your application's own log, showing what the user was doing in the minutes before, often tells you how it got there.
BugSplat lets you attach those logs (along with screenshots, config files, or repro videos) directly to the crash report, so the stack trace and the log that explains it sit in one place. For QA and support teams that kills the "can you send me your logs?" back-and-forth entirely. The crash arrives with its own context attached. Logs answer the questions a stack trace can't, and keeping them with the crash is what turns two half-pictures into one full one.
The short version
When an access violation hits: check whether you were reading or writing, look at the faulting address (small means null, large means wild), and find the bad pointer. A symbolicated report with the local variables attached usually hands you the culprit on sight, and the log riding along with it tells you how the pointer went bad in the first place.
If you want to see it on your own crashes, you can start free and submit a test crash to watch an access violation come back fully symbolicated, variables and all.