Why can I access a member of the structure after the pointer to it is freed?

If I define the structure ...

struct LinkNode { int node_val; struct LinkNode *next_node; }; 

and then create a pointer to it ...

 struct LinkNode *mynode = malloc(sizeof(struct LinkNode)); 

... and then finally free () it ...

 free(mynode); 

... I can still access the element 'next_node' of the structure.

 mynode->next_node 

My question is: what part of the basic mechanics tracks the fact that this memory block should represent the LinkNode structure? I am new to C, and I expected that after I used free () in a pointer to my LinkNode, I would no longer be able to access members of this structure. I was expecting some kind of warning "no longer available."

I would like to know more about how the basic process works.

+7
source share
6 answers

The compiled program no longer knows about struct LinkedNode or a field named next_node , or something like that. Any names completely disappeared from the compiled program. The compiled program works in terms of numerical values, which can play the role of memory addresses, offsets, indices, etc.

In your example, when you read mynode->next_node in the source code of your program, it compiles to machine code, which simply reads a 4-byte numeric value from some reserved memory location (the so-called variable mynode in your source code) adds 4 (which is the offset of the next_node field) and reads a 4-byte value at the resulting address (which is equal to mynode->next_node ). This code, as you can see, works in terms of integer values โ€‹โ€‹- addresses, sizes and offsets. He is not interested in any names, such as LinkedNode or next_node . It does not matter if memory is allocated and / or freed. It does not matter if any of these access is legal or not.

(The constant 4, which I reuse in the above example, is specific to 32-bit platforms. On 64-bit platforms, it will be replaced by 8 in most (or all) instances.)

If an attempt is made to free memory, these calls may cause your program to crash. Or they canโ€™t. This is a matter of sheer luck. As for the language, the behavior is undefined.

+6
source

No, and you cannot. This is a classic case of undefined behavior.

When you have undefined behavior, anything can happen. Perhaps it even works, only for accidental failure after a year.

+4
source

This works by sheer luck, because the freed memory has not yet been overwritten by something else. Once you free memory, your responsibility is not to use it again.

+4
source

No part of the main memory tracks it. This is just the semantics that a programming language gives a piece of memory. You can, for example, distinguish it from something completely different and still access the same memory area. However, the catch here is that it is more likely to lead to errors. Moreover, the equipment will be saved. In your case, just because you called free does not mean that the underlying memory is compressed at all. Your operating system has only a flag that marks this region again.

Think of it this way: a free function is something like a โ€œminimalโ€ memory management system. If your call requires more than setting a flag, this will lead to unnecessary overhead. Also, when you access a member, you (that is, your operating system) can check whether the flag for this memory area is set to โ€œfreeโ€ or โ€œusedโ€. But again overhead.

Of course, this does not mean that it would not make sense to do such things. This avoids many security issues and is performed, for example, in .NET and Java. But these operating times are much younger than C, and today we have much more resources.

+2
source

When your compiler translates your C code into executable machine code, most of the information is deleted, including type information. Where do you write:

  int x = 42; 

the generated code simply copies a specific bit pattern into a specific part of the memory (a piece, which can usually be 4 bytes). You cannot say, having studied machine code, that a piece of memory is an object of type int .

Similarly, when you write:

 if (mynode->next_node == NULL) { /* ... */ } 

the generated code will retrieve a piece of memory with the size of the pointer, dereferencing another piece of memory with the size of the pointer and comparing the result with the system representation of the null pointer (usually all bits are zero). The generated code does not directly reflect the fact that next_node is a member of the structure or something about how the structure was distributed or whether it exists.

The compiler can check many things at compile time, but it does not necessarily generate code to perform checks at runtime. It is up to you as a programmer to avoid mistakes in the first place.

In this particular case, after calling free , mynode has an undefined value. It does not point to any valid object, but there are no requirements for the implementation to do anything with this knowledge. The free call does not destroy the allocated memory, it just makes it available for allocation by future malloc calls.

There are several ways to implement such checks, such as implementation, and throwing a runtime error if you search for a pointer after free . But such checks are not required in C, and they are usually not implemented, because (a) they would be quite expensive, making your program run slower, and (b) the checks cannot catch all the errors.

C is defined so that memory allocation and pointer manipulation will work correctly if your program does everything right. If you make certain errors that can be detected during compilation, the compiler can diagnose them. For example, assigning a pointer value to an integer requires at least a compile-time warning. But other errors, such as dereferencing the free d pointer, cause your program to have undefined behavior. It is up to you as a programmer to avoid these errors in the first place. If you fail, you are on your own.

Of course, there are tools that can help. Valgrind - alone; smart optimizing compilers is different. (Enabling optimization forces the compiler to do more analysis of your code and often allows it to diagnose more errors.) But ultimately, C is not the language that holds your hand. This is a sharp tool - and one that can be used to create safer tools, such as interpreted languages, that do a lot of checking at runtime.

+2
source

You need to assign NULL to mynode-> next_node:

mynode-> next_node = NULL;

after freeing the memory so that it indicates that you are no longer using the allocated memory.

Without nulling, it still points to a previously freed memory location.

0
source

All Articles