0

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.

Robin Hellmers
  • 214
  • 1
  • 11
  • 2
    array indexing is identical to pointer arithmetic: a[b] = *(a+b) = *(b+a) = b[a] Have a look at this https://stackoverflow.com/questions/381542/with-arrays-why-is-it-the-case-that-a5-5a – Jerry Jeremiah Dec 04 '19 at 21:08
  • Yes, you can use a pointer as if it were an array, but *only if the pointer points to valid memory*. If it doesn't (as in your `int* ptr;` example), bad things happen. Please see [this answer](https://stackoverflow.com/questions/37087286/c-program-crashes-when-adding-an-extra-int/37087465#37087465) to a similar question. – Steve Summit Dec 04 '19 at 21:27
  • Yes Jerry, my fault of not being consistent. – Robin Hellmers Dec 05 '19 at 10:07

2 Answers2

0

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;
}
Bruno Rijsman
  • 3,715
  • 4
  • 31
  • 61
  • Then if I initialize an integer `int myint = 5`, then a pointer to that integer address `int* ptr = &myint` and lastly start using `ptr` as an array by `ptr[1] = 6`, `ptr[2] = 7`. Then I would guess that only the first address to the value `5` would be valid and the next two values might result in memory corruption or segmentation fault? – Robin Hellmers Dec 05 '19 at 10:12
  • @robin Correct, ptr[0] would be safe, but ptr[1], ptr[2] etc. would be unsafe. I will update the answer to clarify that. – Bruno Rijsman Dec 05 '19 at 13:31
0

When you declare a local variable it is initialized with a random value that was in the stack. So when you say int *x; the value of that int* is essentially random. The problem of using the pointer in this state is you don't know where it's pointing. Is it valid memory? Is it pointing to memory you don't want to overwrite?

You must initialize a pointer with some value before using it if you expect it to work reliably. This can be an array on the stack or a memory location reserved by malloc.

foreverska
  • 585
  • 3
  • 20