Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Resizing a char* on the fly - why does this code *work*? [duplicate]

I'm experimenting with malloc & realloc and came up with some code for the following problem:

I want to create a string of unknown size, without setting any limit. I could ask the user for a nr of chars, but I rather resize the str as the user types each character.

So I was trying to do this with malloc + realloc and the idea was that everytime the user enters a new char, I use realloc to request +1 piece of memory to hold the char.

While trying to implement this, I did a mistake and ended up doing the following:

int main () {
    /* this simulates the source of the chars... */
    /* in reality I would do getch or getchar in the while loop below... */

    char source[10];
    int i, j;
    for (i=0, j=65; i<10; i++, j++) { 
            source[i] = j;
    }

    /* relevant code starts here */

    char *str = malloc(2 * sizeof(char)); /* space for 1 char + '\0' */
    int current_size = 1;

    i = 0;
    while(i<10) {
            char temp = source[i];
            str[current_size-1] = temp;
            str[current_size] = '\0';
            current_size++;
            printf("new str = '%s' | len = %d\n", str, strlen(str));
            i++;
    }

    printf("\nstr final = %s\n", str);

    return 0;

} 

Note that the realloc part is not implemented yet.

I compiled and executed this code and got the following output

new str = 'A' | len = 1
new str = 'AB' | len = 2
new str = 'ABC' | len = 3
new str = 'ABCD' | len = 4
new str = 'ABCDE' | len = 5
new str = 'ABCDEF' | len = 6
new str = 'ABCDEFG' | len = 7
new str = 'ABCDEFGH' | len = 8
new str = 'ABCDEFGHI' | len = 9
new str = 'ABCDEFGHIJ' | len = 10

I found these results weird, because I expected the program to crash: str has space for 2 chars, and the code is adding more than 2 chars to the str without requesting more memory. From my understanding, this means that I'm writing in memory which I don't own, so it should give a runtime error.

So... Why does this work?

(The compiler is GCC 4.3.4.)

Thanks in advance.

EDIT: One of the commenters suggesting that a call to free() could lead to the error being signaled. I tried calling free() with the above code and no error resulted from executing the code. However, after adding more items to the source array, and also calling free, the following error was obtained:

* glibc detected ./prog: free(): invalid next size (fast): 0x09d67008 **

like image 730
MyName Avatar asked Dec 10 '22 00:12

MyName


2 Answers

Since you're writing past the allocated memory, your code has undefined behaviour.

The fact that the code happened to not crash once (or even many times) does not change that.

Undefined behaviour doesn't mean that the code has to crash. In your case, there happens to be some memory immediately following str, which you're overwriting. The actual effects of overwriting that memory are not known (you could be changing the value of some other variable, corrupting the heap, launching a nuclear strike etc).

like image 198
NPE Avatar answered Dec 15 '22 00:12

NPE


seems from glibc-2.14, memory allocate will allocate as the size as follows, and it will set border, so when you allocate 2 byte size " char *str = malloc(2 * sizeof(char))", it seems memory allocated is no less than 16 byte, so you may add more items and then cause the programe error.

struct _bucket_dir bucket_dir[] = {

    { 16,   (struct bucket_desc *) 0},

    { 32,   (struct bucket_desc *) 0},

    { 64,   (struct bucket_desc *) 0},

    { 128,  (struct bucket_desc *) 0},

    { 256,  (struct bucket_desc *) 0},

    { 512,  (struct bucket_desc *) 0},

    { 1024, (struct bucket_desc *) 0},

    { 2048, (struct bucket_desc *) 0},

    { 4096, (struct bucket_desc *) 0},

    { 0,    (struct bucket_desc *) 0}};   /* End of list marker */


void *malloc(unsigned int len)
{
    struct _bucket_dir  *bdir;
    struct bucket_desc  *bdesc;
    void            *retval;

    /*
     * First we search the bucket_dir to find the right bucket change
     * for this request.
     */
    for (bdir = bucket_dir; bdir->size; bdir++)
        if (bdir->size >= len)
            break;
    if (!bdir->size) {
        printk("malloc called with impossibly large argument (%d)\n",
            len);
        panic("malloc: bad arg");
    }
    /*
     * Now we search for a bucket descriptor which has free space
     */
    cli();  /* Avoid race conditions */
    for (bdesc = bdir->chain; bdesc; bdesc = bdesc->next) 
        if (bdesc->freeptr)
            break;
    /*
     * If we didn't find a bucket with free space, then we'll 
     * allocate a new one.
     */
    if (!bdesc) {
        char        *cp;
        int     i;

        if (!free_bucket_desc)  
            init_bucket_desc();
        bdesc = free_bucket_desc;
        free_bucket_desc = bdesc->next;
        bdesc->refcnt = 0;
        bdesc->bucket_size = bdir->size;
        bdesc->page = bdesc->freeptr = (void *) cp = get_free_page();
        if (!cp)
            panic("Out of memory in kernel malloc()");
        /* Set up the chain of free objects */
        for (i=PAGE_SIZE/bdir->size; i > 1; i--) {
            *((char **) cp) = cp + bdir->size;
            cp += bdir->size;
        }
        *((char **) cp) = 0;
        bdesc->next = bdir->chain; /* OK, link it in! */
        bdir->chain = bdesc;
    }
    retval = (void *) bdesc->freeptr;
    bdesc->freeptr = *((void **) retval);
    bdesc->refcnt++;
    sti();  /* OK, we're safe again */
    return(retval);
}
like image 30
vincent Avatar answered Dec 14 '22 22:12

vincent