Other answers are flawless and explain everything perfectly, but I'd like to show you a more practical example since you can have so much fun by playing with C. Have a look at this:
Memory address array1: 0x7ffd58160dea
Memory address array2: 0x7ffd58160de5
Content of array1: 0123456789�
Content of array2: abcde0123456789�
Memory address] Memory content:
0x7ffd58160de5] a
0x7ffd58160de6] b
0x7ffd58160de7] c
0x7ffd58160de8] d
0x7ffd58160de9] e
0x7ffd58160dea] 0
0x7ffd58160deb] 1
0x7ffd58160dec] 2
0x7ffd58160ded] 3
0x7ffd58160dee] 4
0x7ffd58160def] 5
0x7ffd58160df0] 6
0x7ffd58160df1] 7
0x7ffd58160df2] 8
0x7ffd58160df3] 9
0x7ffd58160df4]
0x7ffd58160df5]
Let's consider 2 char arrays array1
and array2
with different lengths and without the terminator character '\0'
.
The following code saves the lowest address of the two arrays ((char*)&array1 < (char*)&array2
) and saves it in startingPtr
, then prints the following 100 char
(byte) of memory starting from startingPtr
, showing both the address and the content:
#include <stdio.h>
int main()
{
char array1[10] = "0123456789";
char array2[5] = "abcde";
char* startingPtr;
printf("Memory address array1: %p\nMemory address array2: %p\n", &array1, &array2);
printf("\nContent of array1: %s\nContent of array2: %s\n", array1, array2);
// Get which one has the lower address
if ((char*)&array1 < (char*)&array2)
startingPtr = (char*)&array1;
else startingPtr = (char*)&array2;
// Print memory content, starting from the memory address of the first array
printf("\nMemory address] Memory content:\n");
for (int i = 0; i < 100; i++)
{
printf("%p] %c\n", &(*(startingPtr + i)), *(startingPtr + i));
}
return 0;
}
Check the output there, with different compilers:
As you can notice, the output can be different for a bunch of reasons (depending on the compiler, the machine, virtual memory, etc.).
But the reason you can sometimes see the content of both the arrays is that it can happen that the Operating System allocates their variables near, in continuous memory addresses. Therefore printf("%s");
, which expects an adequately formatted "string" (i.e. a char buffer with the terminator character at the end), believes that your buffer is longer than 10 or 5 characters, and prints also the following characters.
However, that's definitely not reliable, since it's undefined behaviour.
NB: the notation *(array + index)
is one of the many ways you can access array elements. Since arrays are basically pointers to their first element, that means "get the value stored at memory address array + index", and is equivalent to array[index]
.
printf
to continue into the bytes in memory past theabcde
array which in this case is the0123456789
array. – Bedizenprintf
will likely encounter a null byte (\0
) sooner or later after walking off the end of your array. – TwumBuffer
is at the top of the local stack, and behind that, there's the return address from the call to yourmain
function. It's also likely that this return address, as a number, is small enough to contain at least one zero in its high byte. Which acts as a terminator forprintf
(depending on endianness, this zero might be in memory before or after the other bytes). Then, there may be alignment issues, and there may be a "stack canary" between return address and variables, and none of this is guaranteed - but it's quite likely to find\0
somewhere. – Aleasealeatoryprintf
deals in properly-formed strings. You went out of your way to construct and give it something that wasn't a properly-formed string. Weird behavior is to be expected, and there's nothing too surprising about the particular weird behavior you saw. – Pentagram