Search code examples
carrayspointersmemoryallocation

C: Declaring a pointer and then use it as an array, memory allocation


When discussing pointers and arrays, the explanations often tell that initializing an array and then initializing a pointer to the same memory location does make you able to use that pointer in the same way as the first array:

int myIntArray[3] = {5, 6, 7};
int* ptr = myIntArray; // ptr[2] = myIntArray[2] = 7

I understand that this works as the allocation of memory on the stack is done with the first initialization int myIntArray[3]. But if one creates a pointer, it does not allocate potential memory for an array.

Thereby is my question, is it safe to create a pointer and use it as an array directly or might some other memory used be overwritten or such?

int* ptr;
*ptr = 5;
*(ptr+1) = 6;
*(ptr+2) = 7;

My guess is that if the address (ptr+2) would contain some previously allocated memory, such as an earlier initialized variable, the computer would reallocate ptr and (ptr+1) to some place were (ptr+2) is not used since before.


Solution

  • No, your example is not safe.

    Your ptr variable is not explicitly initialized.

    If ptr is a local variable, it will be initialized to a undefined (i.e. random) value. Thus, *ptr = 5 will write the value 5 to a random memory location. This will cause either a memory corruption or (more likely) a segmentation fault. See the following example:

    #include <stdio.h>
    
    int main(void)
    {
        int *ptr;    /* Local variables are not intitialized and contain a random value */
        printf("ptr contains address %p\n", ptr);   /* Prints a random value */
        ptr[0] = 123;  /* BAD: On macOS causes memory corruption */
        return 0;
    }
    

    See also note [1] below.

    If ptr is a global variable, it will be initialized to zero. This *ptr = 5 will write the value 5 to virtual address 0, which most likely will cause a segmentation fault.

    #include <stdio.h>
    
    int *ptr;  /* Global variables are initialized to zero */
    
    int main(void)
    {
        printf("ptr contains address %p\n", ptr);  /* Prints 0x0 */
        ptr[0] = 123;  /* BAD: On macOS causes "Segmentation fault: 11" */
        return 0;
    }
    

    The same is true for static variables:

    #include <stdio.h>
    
    int main(void)
    {
        static int *ptr;    /* Static variable are initialized to zero */
        printf("ptr contains address %p\n", ptr);   /* Prints 0x0 */
        ptr[0] = 123;  /* BAD: On macOS causes "Segmentation fault: 11" */
        return 0;
    }
    

    If you want to use a pointer that does not point to an array, you should explicitly allocate the memory, for example by calling malloc

    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void)
    {
        int *ptr;    /* Local variables are not intitialized and contain a random value */
    
    /* Explicitly allocate enough memory to store one int value */
        ptr = malloc(sizeof(int));
        if (!ptr) {            
            fprintf(stderr, "Out of memory");
            exit(1);
        }
        printf("ptr contains address %p\n", ptr);   /* Prints a random value "on the heap" */
    
        *ptr = 111;  /* OK */
        printf("*ptr contains value %d\n", *ptr);   /* OK, 111 */
        printf("ptr[0] contains value %d\n", ptr[0]);   /* OK, 111 */
    
        ptr[0] = 222;  /* OK */
        printf("*ptr contains value %d\n", *ptr);   /* OK, 222 */
        printf("ptr[0] contains value %d\n", ptr[0]);   /* OK, 222 */
    
        /* Must explicitly free memory after you are done with it, otherwise you have a memory leak */
        free(ptr);
    
        return 0;
    }
    

    If you make a pointer point to an int, it is safe to read from or write to the de-referenced pointer either using *p or p[0] (which are essentially the same thing), but p[1] is not safe:

    #include <stdio.h>
    
    int main(void)
    {
        int foo = 111;
        int bar = 222;
        int *ptr = &bar;
    
        printf("Address of ptr %p\n", &ptr);   /* An address on the stack */
                                            /* Some random value */
                                            /* Note: this is the address OF the ptr variable, */
                                            /*       and not the address stored IN ptr */
    
        printf("Address of bar %p\n", &bar);   /* An address on the stack */
                                            /* Exact difference with ptr depends on compiler etc. */
                                            /* On macOS: 12 bytes after address of ptr */
    
        printf("Address of foo %p\n", &foo);   /* An address on the stack */
                                            /* Exact difference with bar depends on compiler etc. */
                                            /* On macOS: 4 bytes after address of bar */
    
        printf("foo contains value %d\n", foo);   /* 111 */
        printf("bar contains value %d\n", bar);   /* 222 */
        printf("ptr contains address %p\n", ptr);   /* The same as address of bar above */
    
        ptr[0] = 333;  /* Perfectly safe, changes the value of bar */
    
        printf("bar contains value %d\n", bar);  /* 333 */
    
        ptr[1] = 444;  /* BAD technically the behavior is undefined */
                    /* But most likely will change the value of foo */
                    /* Because most likely foo is stored after bar on the stack */
    
        printf("foo contains value %d\n", foo);  /* On macOS: 444 */
    
        return 0;
    }
    

    Note [1]: Accidentally "buffer overflows" are a very common problem in C programs, particularly when dealing with strings. This is one of the most common mistakes for novice C programmers. See below for an example. Many viruses exploit such bugs to craft a special string to force a very carefully controlled overflow which causes the function return address (which is also stored on the stack) to be overwritten. When the function returns, instead of returning to the calling function, it returns to some address carefully chosen by the attacker. This allows the attacker to execute some code of her choosing. I think I am not exaggerating when I say that a very large fraction of viruses work in this way. So, be careful and pay very close attention to your memory management!!! See https://developer.apple.com/library/archive/documentation/Security/Conceptual/SecureCodingGuide/Articles/BufferOverflows.html for more details.

    #include <stdio.h>
    #include <string.h>
    
    int main(void)
    {
        int foo = 111;
        char str[5];
    
        printf("foo contains value %d\n", foo);   /* 111 */
    
        strcpy(str, "12345678");  /* BAD: Overflow! Writing 9 bytes into a 5 byte string */
                                /* (9 not 8, because of the terminating 0 byte ) */
                                /* Modern operating systems / compilers / processors catch this */
                                /* On my macOS, I get "Abort trap 6" */
    
        /* Technically, behavior is undefined */
        /* In practice, foo is overwritten */
    
        printf("foo contains value %d\n", foo);   /* With older operating systems / compilers / */
                                                /* processors this would show a changed value */
    
    
    
        return 0;
    }