"What’s unusual about this code?"
Nothing unusual, but very wrong. Pointer arithmetic is a widely used feature in C language when one wants to manipulate directly the contents in the memory. So it’s not even something unusual in C. However, the programmer needs to understand very well the use of pointers and know what he’s doing to avoid errors (sometimes very serious). Both to learn and to eventually discover and correct mistakes, the debugger is your best friend.
Using the Visual Studio 2012 debugger, I follow a line-by-line analysis of your code.
1. First step
int *ptr = (int *) malloc(sizeof(int));
What this first line does is simply allocate a fixed-size memory area, defined by sizeof(int)
, and store this address in the variable ptr
. There are already some important considerations. First, the allocated size will depend on the platform/operating system in use and also the configuration used in the compiler. In a 32-bit system, the integer uses 4 bytes for storage (i.e., numbers represented in binary with up to 32 bits), in a 64-bit system uses 8 bytes, and so on. That’s why the platform defines the largest integer that can be addressed (for the counting of Handles files, for example). In addition, the compiler can be configured to generate code for a specific platform. In my example, I am using Visual Studio 2012 on a 64-bit Windows machine, but compiling my code intentionally for 32-bit. Therefore, the result of sizeof(int)
in my case would equal 4
.
In addition, the function malloc
allocates a memory area of the given size (in bytes) and returns an address for the first byte of that area in memory. That address is, of course, addressed (intentional redundancy here) using whole platform numbers (which is why a 64-bit machine can have more memory, since its integer is larger and can reach longer addresses). In other words, it doesn’t matter if your pointer is a char*
one struct longa_estrutura*
or odiabo*
, your address will be an integer number, usually represented in hexadecimal format 0x000A1F23
. If you have questions about the use of pointers, this another question must be of some help.
So, when running this line what you have (considering that in my example I am running a program compiled for 32 bits) is a memory area of 4 bytes allocated somewhere, whose initial address is stored in the variable ptr
(in the example, the address 0x00773fe0
):
Note how the first four bytes from the starting position contain
the value cd
. This is because I ran in mode
debug, and so the Microsoft compiler initializes memory areas with standardized values. This does not happen in this compiler
if the programme is executed in release (on grounds of
performance), and so could have there any garbage arising from
previous executions of other programmes.
2. Second step
ptr[0] = 5;
In this line occurs the first action of pointer "arithmetic". It is called so because, in the language C, access ptr[0]
is equivalent to access ptr+0
.
You may even have another variable that points to any "position" in
memory from the initial pointer, for example int *ptr3 = ptr+3
will "point" to the position in ptr[3]
(that is, in the last byte of memory allocated).
And what you’re doing is modifying the value of that memory area, from the initial address indicated, putting the entire value there 5
(101
in binary, or 05
in hexadecimal). Therefore its result in memory is as follows:
Note how the first four bytes were changed (Visual Studio is Tom and indicates this with the red color).
3. Step 3
ptr[1] = 10;
Here again there is an arithmetic of pointers (equivalent to ptr+1
). But there is one very important question. Since the pointer was declared as a pointer to an integer type (of 4 bytes in my example), the sum made by the compiler is not equivalent to 1 byte, but 4 bytes! So, in practice, this arithmetic ptr+1
will, in terms of memory, amount to ptr+4
bytes. And therefore your memory output will be as follows:
And that’s where the mistake is. With this manipulation, you are "invading" an area of memory that has not been allocated to your program (i.e., writing beyond the space that has actually been reserved). And there can occur numerous scenarios: you can invade an area that is not being used, and nothing happens; you can invade an area of memory of another program, which will be prevented by the operating system causing an error in your program; you can change the value of any variable from any other part of your own program, causing bizarre results of the most diverse, etc.
4. Additional example
A pointer arithmetic that would be more careful and would not generate errors would need to ensure that data is never written/read beyond the actual reserved area. An initial step can be to manipulate the pointer as char *
instead of int *
, with the code like this:
char *ptr = (char *) malloc(sizeof(int));
ptr[0] = 5;
ptr[1] = 10;
free(ptr);
Thus the result of the penultimate line (ptr[1] = 10;
) would generate update in memory only in the second byte (since char
is 1 byte in size):
Still, care must be taken with the value that is assigned. The decimal value 10
(hexadecimally, 0a
) requires only 1 byte to be represented, but any decimal value above 255
(FF
in hexadecimal) would need more than 1 byte and could invade the memory if not taken care of (for example, an upgrade in 4 byte as ptr[3] = 10
is ok, but how ptr[3] = 256
is not ok!).
Not making a mistake is just bad luck. Not making a mistake leads you to think that "it works". But it doesn’t work. What’s happening is the famous Undefined behaviour: anything can happen. The program can work as if it is well written, can crash immediately, can crash when the boss is watching, can paint the ceiling yellow, ..., ...
– pmg