8

Why does that work?

#include <iostream>
using namespace std;

int main() {
    float* tab[3];

    int i = 0;
    while(i < 3) {
        tab[i] = new float[3-i];
        i++;
    }

    cout << tab[2][7] << endl;
    tab[2][7] = 6.87;
    cout << tab[2][7] << endl;

    i = 0;
    while(i < 3)
        delete[] tab[i];
}

while this one doesn't?

#include <iostream>
using namespace std;

int main() {
    float* tab = new float[3];

    cout << tab[7] << endl;
    tab[7] = 6.87;
    cout << tab[7] << endl;

    delete[] tab;
}

I tried both programs on Win XP with MS VS 2008, both compiled without errors and the first one ran without any errors. The second made pop up some error window, however I can't remember it and can't reproduce (no access to Windows at the moment).

I tried them also on Linux (Kubuntu 10.10 with precompiled kernel package version 2.6.35.23.25) with g++ and both compile and run without any errors.

Why? Shouldn't there be any pop-ups with something like "Wrong access to unallocated memory"?

I know it should (and, luckily, does) compile without errors, but I thought it shouldn't run without them... And why the second example makes errors on Windows and not on Linux?

silmeth
  • 680
  • 1
  • 8
  • 22

5 Answers5

10

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".

Steve Townsend
  • 53,498
  • 9
  • 91
  • 140
8

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.

Edward Strange
  • 40,307
  • 7
  • 73
  • 125
5

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.

EboMike
  • 76,846
  • 14
  • 164
  • 167
1

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.

Mark Storer
  • 15,672
  • 3
  • 42
  • 80
0

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).

Community
  • 1
  • 1
Eclipse
  • 44,851
  • 20
  • 112
  • 171
  • Even then std::vector only bounds checks in debug builds (thankfully!) – Martin Beckett Nov 30 '10 at 18:08
  • Unless you use `vector::at()`, or work with iterators rather than indexes. – Eclipse Nov 30 '10 at 18:12
  • Actually, this one's wrong quite a bit too. First off, there's no such thing as "pages" in the standard and not all implementations will work the way you're claiming they do in that regard. Second of all, you can only overwrite the stack (on systems that even use one) when the array is located on it. Memory allocated from the free-store is not on the stack. The kind of attack you're talking about happens when you do something like `void f() { char buf[SZ]; gets(buf); }`, which isn't the OP's issue. – Edward Strange Nov 30 '10 at 18:30
  • @Noah: He's asking why it's working the way it does. He's obviously hit undefined behaviour, and whatever happens, happens. But in most (all) gcc on Linux or VC on Windows implementations, it's going to be allocating pages when you call new(). – Eclipse Nov 30 '10 at 18:36
  • @Noah: Yes, he's not going to hit the stack in this specific case, but it is a possible outcome of going out of bounds in other cases. Buffer overflows in the heap can be just as dangerous, as all one needs to do is overwrite a single function pointer (or even vtable). – Eclipse Nov 30 '10 at 18:37