Why does that work?
#include
using namespace std;
int main() {
float* tab[3];
int i = 0;
while(i < 3) {
tab[i] = ne
Memory access protection isn't very fine grained. When you allocate some memory, you get a whole page worth of memory allocated to your program. When you try and access that extra memory, it's likely to succeed, but you're also likely to run over other memory allocated to your program.
This is why buffer overruns work as an attack. In many cases it's predictable what that extra memory after your array is being used for. If I can control what you put there, I can overwrite data you don't want me overwriting. If I can overwrite your call stack, then I can execute any code I want in your process context. If this is a service running as an admin user, then I have a local privilege escalation. If this is a internet-facing service of some sort, then I have a remote execution attack.
Your best bet is to work with more robust structures like std::vector unless you have a specific purpose for using arrays. (And even then you might be able to get away with vectors).
While the other answers, with the exception of Mark's, are not wrong they are not exactly right either. What you are doing by accessing data past the end of what you've explicitly allocated in your program is causing "undefined behavior". It can do anything, including "work".
Steve's answer didn't exist when I began writing this.
Both of them do out-of-bounds array access - you have an array of 3 float pointers, and you're accessing the 8th array. This is bound to crash.
However, unlike Java or some other managed languages, there is no explicit bounds checking for each array access (since the performance cost of that is prohibitive). So the only bounds checking you have is your MMU. If you end up accessing memory that does not belong to your application, you'll crash. If you hit memory that is not allocated but still happens to be part of your process (could be a guard word, for example), it'll silently succeed. This is a great recipe for very-hard-to-track-down bugs.
Bounds checking is key. Do it whenever you can.
Use of unallocated memory results in undefined behaviour. You can have no expectations of what will happen when you do this even on the same system and compiler, let alone across different combinations of hardware and compiler.
The program might crash immediately, it might work for a while and then fail later, it might even appear to work perfectly.
Accessing memory you don't own is always a programming error, though. Don't think of the appearance of correct operation as "it sometimes works", think of it as "I got really unlucky and my bug does not show up quickly".
In the first example, tab[2] has a value that points to valid memory. tab[2]+7 isn't allocated, But It Could Be. No seg-fault.
In the second, tab[7] has no value... it's random bits (possibly zeros, or 0xDEADBEEF or just whatever value was there last). This almost certainly does not point to memory that is valid for this application to access. Thus: boom.