views:

636

answers:

11

Hi! I realize the code sample below is something you should never do. My question is just one of interest. If you allocate a block of memory, and then move the pointer (a no-no), when you deallocate the memory, what is the size of the block that is deallocated, and where is it in memory? Here's the contrived code snippet:

#include <stdio.h>
#include <string.h>

int main(void) {
    char* s = malloc(1024);
    strcpy(s, "Some string");
    // Advance the pointer...
    s += 5;
    // Prints "string"
    printf("%s\n", s);
    /*
     * What exactly are the beginning and end points of the memory 
     * block now being deallocated?
     */
    free(s);
    return 0;
}

Here is what I think I happens. The memory block being deallocated begins with the byte that holds the letter "s" in "string". The 5 bytes that held "Some " are now lost.

What I'm wondering is: Are the 5 bytes whose location in memory immediately follows the end of the original 1024 bytes deallocated as well, or are they just left alone?

Anyone know for sure what is it the compiler does? Is it undefined?

Thanks.

+4  A: 

Yes, it is undefined behavior. You're essentially freeing a pointer you didn't malloc.

Mehrdad Afshari
even worse I would expect it to crash with most malloc libs.
Fredrik
Undefined behavior means "anything can happen." From blowing up your machine along with yourself to working correctly.
Mehrdad Afshari
Of course, I just wanted to emphasize that to the OP.
Fredrik
The common idiom we use at my work for undefined behaviour is 'email your mother'
Salgar
+17  A: 

You cannot pass a pointer that was not obtained from a malloc, calloc or realloc to free (except NULL).

Question 7.19 in the C FAQ is relevant to your question.

The consequences of invoking undefined behavior are explained here.

Sinan Ünür
Thanks. I'm reviewing C now, after years of not looking at it. I never did anything with it beyond pedagogical, toy programs. I did at one time more or less understand pointers, and it seems to be coming back to me ;-)Your comment reminds me that the pointer variable is not the same thing as the pointer, itself. Once the address stored in "s" is changed, it's still the same variable but no longer the same pointer.
Mario
@Mario You are welcome.
Sinan Ünür
+2  A: 

It is not the compiler that does it, it is the standard library. The behavior is undefined. The library knows that it allocated the original s to you. The s+5 is not assigned to any memory block known by the library, even though it happens to be inside a known block. So, it won't work.

Juliano
+4  A: 

You cannot pass a pointer you did not obtain from malloc (or calloc or realloc...) to free. That includes offsets into blocks you did obtain from malloc. Breaking this rule could result in anything happening. Usually this ends up being the worst imaginable possibility at the worst possible moment.

As a further note, if you wanted to truncate the block, there's a legal way to do this:

#include <stdio.h>
#include <string.h>

int main() {
    char *new_s;
    char *s = malloc(1024);
    strcpy(s, "Some string");

    new_s = realloc(s, 5);
    if (!new_s) {
        printf("Out of memory! How did this happen when we were freeing memory? What a cruel world!\n");
        abort();
    }
    s = new_s;

    s[4] = 0; // put the null terminator back on
    printf("%s\n", s); // prints Some

    free(s);
    return 0;
}

realloc works both to enlarge and shrink memory blocks, but may (or may not) move the memory to do so.

bdonlan
+2  A: 

What I'm wondering is: Are the 5 bytes whose location in memory immediately follows the end of the original 1024 bytes deallocated as well, or are they just left alone?

Both. The result is undefined so a compiler is free to do either of those, or anything else they'd like really. Of course (as with all cases of "undefined behavior") for a particular platform and compiler there is a specific answer, but any code that relies on such behavior is a bad idea.

Andrew Khosravian
+1  A: 

Adding to the more formal answers: I'd compare the mechanics of this to one taking a book in the library (malloc), then tearing off a few dozen pages together with the cover (advance the pointer), and then attempting to return it (free).

You might find a librarian (malloc/free library implementation) that takes such a book back, but in a lot of case I'd expect you would pay a fine for negligent handling.

In the draft of C99 (I don't have the final C99 handy in front of me), there is something to say on this topic:

The free function causes the space pointed to by ptr to be deallocated, that is, made available for further allocation. If ptr is a null pointer, no action occurs. Otherwise, if the argument does not match a pointer earlier returned by the calloc, malloc, or realloc function, or if the space has been deallocated by a call to free or realloc, the behaviour is undefined.

In my experience, a double free or the free of the "pointer" that was not returned via malloc will result in a memory corruption and/or crash, depending on your malloc implementation. The security people from both sides of the fence used this behaviour not once, in order to do various interesting things at least in early versions of the widely used Doug Lea's malloc package.

Andrew Y
A: 

Short version: It's undefined behavior.

Long version: I checked the CWE site and found that, while it's a bad idea all around, nobody seemed to have a solid answer. Probably because it's undefined.

My guess is that most implementations, assuming they don't crash, would either free 1019 bytes (in your example), or else free 1024 and get a double free or similar on the last five. Just speaking theoretically for now, it depends on whether the malloc routine's internal storage tables contains an address and a length, or a start address and an end address.

In any case, it's clearly not a good idea. :-)

Platinum Azure
Most implementations will crash because they look for internal tracking data immediately before the pointer :)
bdonlan
Or at least corrupt the heap. Crashing immediately is the best possible behavior here.
David Thornley
Sorry, neglected to note crashing. I guess I'd forgotten since other people had mentioned it and I was trying to establish what would happen if a crash didn't occur. To whoever downvoted me, unless you have a different issue with my response please reconsider, as I the crash possibility IS in fact obvious enough such that even I know about it. Thanks :-)
Platinum Azure
It's not downvoting you, it's downvoting the comment. It's not about what you know, it's about the quality of what you actually write down.Also, I think the rest of your answer is wrong as well; how do you expect that the malloc implementation would look up either the length or the end address for s if you give it a different pointer to s+5 that it never allocated?
Brooks Moses
A: 

The library implementation might put some data structure before the pointer it returns to you. Then in free() it decrements the pointer to get at the data structure telling it how to place the memory back into the free pool. So the 5 bytes at the beginning of your string "Some " is interpreted as the end of the struct used by the malloc() algorithm. Perhaps the end of a 32 bit value, like the size of memory allocated, or a link in a linked list. It depends on the implementation. Whatever the details, it'll just crash your program. As Sinan points out, if you're lucky!

**Undefined means undefined**. You would be lucky if it just crashed your program.
Sinan Ünür
+6  A: 

It's undefined behavior in the standard, so you can't rely on anything.

Remember that blocks are artificially delimited areas of memory, and don't automatically show up. Something has to keep track of the block, in order to free everything necessary and nothing more. There's no possible termination, like C strings, since there's no value or combination of values that can be guaranteed not to be inside the block.

Last I looked, there were two basic implementation practices.

One is to keep a separate record of allocated blocks, along with the address allocated. The free() function looks up the block to see what to free. In this case, it's likely to simply not find it, and may well just do nothing. Memory leak. There are, however, no guarantees.

One is to keep the block information in a part of memory just before the allocation address. In this case, free() is using part of the block as a block descriptor, and depending on what's stored there (which could be anything) it will free something. It could be an area that's too small, or an area that's too large. Heap corruption is very likely.

So, I'd expect either a memory leak (nothing gets freed), or heap corruption (too much is marked free, and then reallocated).

David Thornley
A: 

Let's be smart here... free() is not a black hole. At the very least, you have the CRT source code. Beyond that, you need the kernel source code.

Sure, the behavior is undefined in that it is up to the CRT/OS to decide what to do. But that doesn't prevent you from finding out what your platform actualy does.

A quick look into the Windows CRT shows that free() leads right to HeapFree() using a CRT specific heap. Beoyond that you're into RtlHeapFree() and then into system space (NTOSKRN.EXE) with the memory manager Mm*().

There are consistancey checks throughout all these code paths. But doing differnt things to the memory will cause differnt code paths. Hence the true definition of undefined.

At a quick glance, I can see that an allocated block of memory has a marker at the end. When the memory is freed, each byte is written over with a distinct byte. The runtime can do a check to see if the end of block marker was overwritten and raise an exception if so.

This is a posiblility in your case of freeing memory a few bytes into your block (or over-writing your allocated size). Of course you can trick this and write the end of block marker yourself at the correct location. This will get you past the CRT check, but as the code-path goes futher, more undefined behavoir occurs. Three things can happen: 1) absolutely no harm, 2) memory corruption within the CRT heap, or 3) a thrown exception by any of the memory management functions.

Asher
https://www.securecoding.cert.org/confluence/display/seccode/MSC15-C.+Do+not+depend+on+undefined+behavior
Sinan Ünür
http://c-faq.com/ansi/appalled.html
Sinan Ünür
A: 

Calling free() on a ptr that wasnt allocated by malloc or its brethren is undefined.

Most implementations of malloc allocate a small (typically 4byte) header region immediately before the ptr returned. Which means when you allocated 1024 bytes, malloc actually reserved 1028 bytes. When free( ptr ) is called, if ptr is not 0, it inspects the data at ptr - sizeof(header). Some allocators implement a sanity check, to make sure its a valid header, and which might detect a bad ptr, and assert or exit. If there is no sanity check, or it erroneously passes, free routine will act on whatever data happens to be in the header.

Sanjaya R