Malloc improperly reserving memory?

Asked

Viewed 475 times

6

I am studying dynamic memory allocation and came across an error that apparently the compiler is not warning about. The code is very simple:

int *ptr = (int *) malloc(sizeof(int));
ptr[0] = 5;
ptr[1] = 10;
free(p);

This code works normally but it is not allocating memory improperly? See that pointer ptr in the index [1] does not exist, but can assign a value to it. With a printf values are also displayed correctly. What’s unusual about this code?

  • 4

    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, ..., ...

2 answers

5


"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):

inserir a descrição da imagem aqui

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:

inserir a descrição da imagem aqui

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:

inserir a descrição da imagem aqui

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):

inserir a descrição da imagem aqui

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!).

  • 1

    Very good, I don’t have the patience to write this down with so many details and illustrations. Usually the response of programmers who know C is RTFM =D. +1

  • Thank you! Really. Reply RTFM and not answer anything only are not equivalent because the second does not occupy space and is still more educated. :)

4

André,

The function malloc finds spaces in the memory that are available. Using the cited form, only the address of ptr[0] will be "reserved" for your program, with this, your program may present problems in its execution because:

  1. The memory you will use after the size of the "reserved" memory may belong to another application.

  2. The bits that you use the most, may belong to your own application.

  3. Use memory spaces reserved, causing a crash in your application.

This type of behavior is known as Buffer Overflow

To increase the size of the reserved space with malloc, it is possible to use the function realloc.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.