What normally one calls a "crash" happens when some sort of unexpected error is detected in the running program, causing the program to stop in a way other than expected (without saving your files, or resetting the display, for example.) These unexpected errors can be detected by the program itself ("couldn't allocate data"), by the operating system of the computer its running on ("you tried to read data into the memory that you failed to allocate"), or by the hardware itself (in which case, hopefully the operating system notices, and prints a meaningful error message) ("you tried to copy data to a memory address that doesn't exist. Segmentation Violation!")
Typically when this happens, your program is aborted, the operating system cleans up the loose ends that it knows about, and you get data written to the console or some log file that helps you figure out what is going on so that you can fix it. If you have a "real" debugger (or maybe good debugging code inside your program itself), perhaps it sits in between your program and the OS, and lets you poke around AFTER the event causing the crash, but before all that cleanup is done.
Now, on an Arduino-class board:
- There is no operating system.
- The hardware does NOT detect errors like bad opcodes or invalid memory accesses, so even if there was an OS, it wouldn't notice most errors.
- There isn't really any place to report errors.
- The Arduino functions like digitalWrite() do check for some errors, but there's not much that can be done other than ignore them. If you do "digitalWrite(123, 123);" the code is (barely) smart enough not to try to turn on some non-existent port on some non-existent pin, but it doesn't have many options on reporting the error. The function will just return without doing anything.
In effect, the Arduino doesn't actually "crash"; it just goes and executes code somewhere that isn't the code that it's supposed to be running. :-(
Sometimes, perhaps even "often", "the wrong area of code" will be areas beyond the end of the user program, or areas at the beginning of program memory (which happens when you overwrite the return address of a function with a small integer.)
In these cases, the AVR processor is likely to encounter code that will eventually cause the application to "noticeably crashed.) ("blank" program memory contains useless instructions that will eventually roll over into the bootloader and probably restart the sketch, and low program memory contains a bunch of "jump" instructions to a default "unused interrupt handler" that might restart the sketch (AVR) or infinitely loop doing nothing (ARM)
But such behavior is far from guaranteed!