Professional Documents
Culture Documents
nil is used to indicate that a pointer is not currently referencing any memory cell,
essentially pointing to nothing.
2. Uses of Pointers:
3. Heap-Dynamic Variables:
Not Structured Types: Pointers are distinct from structured types like arrays and
records. While arrays and records organize data into structured formats,
pointers simply hold memory addresses.
Different from Scalar Variables: Scalar variables store data directly, whereas
pointers store memory addresses that reference other variables or data.
Reference Types vs. Value Types:
In Fortran 77, a language lacking support for pointers and dynamic memory allocation,
implementing dynamic data structures like a binary tree becomes challenging.
Since dynamic memory allocation is not available, the programmer must maintain
a pool of available tree nodes beforehand. This pool would consist of preallocated
memory blocks that can be used to create tree nodes as needed.
Managing this pool of nodes requires careful bookkeeping to keep track of which
nodes are currently in use and which ones are available for allocation.
Parallel Arrays:
To represent the binary tree structure without pointers, programmers might resort
to using parallel arrays.
One array might store the data associated with each node, while another array
could store indices or references indicating the parent-child relationships
between nodes.
Moreover, the need to guess the maximum number of nodes limits the flexibility
and scalability of the program, as it cannot efficiently handle cases where the
actual number of nodes exceeds the estimated limit.
Overall, managing a dynamic data structure like a binary tree without pointers in
Fortran 77 is cumbersome, error-prone, and inefficient. The lack of dynamic
memory allocation and pointer support imposes significant limitations on the
programmer, making it challenging to implement and maintain complex data
structures.
Design Issues :The primary design issues particular to pointers are the following:
• Are pointers restricted as to the type of value to which they can point?
• Are pointers used for dynamic storage management, indirect addressing, or both?
Languages that provide a pointer type usually include two fundamental pointer operations:
assignment and dereferencing
Assignment Operation:
If pointers are used for managing dynamic storage, their initialization typically
occurs through memory allocation mechanisms, either by operators or built-in
subprograms.
Dereferencing involves accessing the value stored at the memory address pointed
to by the pointer variable.
For example, in C++, dereferencing is explicitly specified using the asterisk (*) as
a prefix unary operator.
Example
In FORTRAN
program implicit_dereferencing
implicit none
integer :: arr(3)
arr = [1, 2, 3]
print *, ptr ! This will print the value stored at the memory location ptr is pointing to
In this Fortran example, ptr implicitly points to the first element of the array arr. When we print
ptr, Fortran automatically dereferences the pointer and prints the value stored at that memory
location.
Example:
In C++
#include <iostream>
int main() {
std::cout << "Dereferenced value of ptr: " << *ptr << std::endl; // Dereferencing ptr to get the
value stored at that memory location
return 0;
In this C++ example, ptr is explicitly dereferenced using the asterisk (*) operator to access the
value stored at the memory address it points to.
In C/C++, there are two common ways to reference fields in a record using a
pointer. One involves explicit dereferencing with (*p).age, while the other uses
the arrow operator ->, combining dereferencing and field reference as p->age.
Languages that support dynamic memory allocation using pointers must include
explicit allocation operations.
j = *ptr
sets j to 206.
Pointer Problems:
When a pointer is left pointing to a memory location that has been deallocated, it
becomes a dangling pointer because it no longer points to a valid memory
location.
They may point to a memory location that has been reallocated to a new
heap-dynamic variable. This can lead to invalid type checks or accessing
unrelated data.
Even if the new dynamic variable is of the same type, its value has no
relationship to the old pointer's dereferenced value.
This can happen, for example, when one pointer is assigned the value of another
pointer, and then the original pointer is deallocated without updating the second
pointer.
Example in C++:
In C++, both arrayPtr1 and arrayPtr2 become dangling pointers after the
deallocation, as the delete operator does not change the value of the pointer itself.
#include <iostream>
int main() {
int* arrayPtr1;
int* arrayPtr2;
arrayPtr1 = arrayPtr2;
// At this point, arrayPtr1 becomes a dangling pointer because it still holds the address
// that was previously allocated to arrayPtr2, but that memory has been deallocated.
// This could lead to undefined behavior such as segmentation fault or unexpected data.
std::cout << "Accessing using dangling pointer arrayPtr1: " << arrayPtr1[0] << std::endl;
return 0;
To prevent dangling pointers, it's important to ensure that pointers are always
pointing to valid memory locations.
When deallocating memory, it's best practice to set the pointer to null or nil to
avoid leaving it as a dangling pointer.
These variables are often referred to as "garbage" because they are no longer useful for
their original purpose, and they cannot be reallocated for any new use within the
program.
In simpler terms, when memory is allocated dynamically on the heap, but the program
loses track of it without deallocating it, it leads to memory leakage.
Lost heap-dynamic variables are typically created when a pointer initially points to a
newly allocated heap-dynamic variable, but is later reassigned to point to a different
dynamically allocated variable.
This sequence of operations results in the first dynamically allocated variable becoming
inaccessible or lost because the program no longer has a reference to it.
Memory Leakage:
Lost heap-dynamic variables can lead to inefficient memory usage, which can degrade
the performance of the program.
Language designers and developers need to address the issue of memory leaks to
ensure the stability and efficiency of software.
Using tools and utilities for detecting and debugging memory leaks during the
development and testing phases of software.
Example:
#include <iostream>
int main() {
arrayPtr[i] = i * 2;
// Assume some other part of the program reassigns arrayPtr to point to a different
memory block
// At this point, the memory block allocated for the initial array is lost
// ...
delete[] arrayPtr;
return 0;
Usage of Memory Block: The program uses the allocated memory block to perform
some operations, such as initializing the array elements.
Reassignment of Pointer: At some point, another part of the program reassigns
arrayPtr to point to a different dynamically allocated memory block of size 200.
However, the program does not deallocate the memory block previously pointed to by
arrayPtr.
Lost Heap-Dynamic Variable: After the reassignment, the memory block originally
allocated for the integer array (size 100) becomes inaccessible to the program. Since the
program no longer has a reference to this memory block and hasn't deallocated it, it
becomes a lost heap-dynamic variable.
Dealing with Memory Leaks: To address memory leaks, it's essential to ensure that
dynamically allocated memory blocks are properly deallocated when they're no longer
needed. In this example, the program should have deallocated the memory block
originally allocated for the integer array (size 100) before reassigning arrayPtr to point
to a new memory block. This can be done using the delete[] operator.
Pointers in Ada
Ada's pointers are referred to as access types. These types allow programmers to
manipulate memory directly, providing a mechanism for dynamic memory
allocation.
This design choice aims to reduce the need for explicit deallocation, which can
lead to dangling pointers if not properly managed.
Limitations in Implementation:
However, the passage notes that few, if any, Ada compilers actually implement
this form of garbage collection.
Ada's design also reduces the likelihood of dangling pointers by restricting access
to heap-dynamic variables.
Unchecked_Deallocation:
The name of this operation is meant to discourage its use or at least warn the user
of its potential problems.
Despite Ada's design efforts, the issue of lost heap-dynamic variables is not fully
eliminated.
Lost heap-dynamic variables can still occur when dynamically allocated memory
blocks become inaccessible or unreachable without being deallocated.
However, they must be used with great care due to the potential for errors such as
dangling pointers and lost heap-dynamic variables.
In C and C++, the asterisk (*) denotes the dereferencing operation, while the
ampersand (&) denotes the operator for producing the address of a variable.
For example, ptr = &init; assigns the address of init to ptr, and count = *ptr;
dereferences ptr to assign the value at init to count.
Pointer Arithmetic:
For example, ptr + index is a legal expression that calculates a new memory
address based on the index and the size of the data type pointed to by ptr.
Array Manipulation:
In C and C++, arrays use zero as the lower bound of their subscript ranges, and
array names without subscripts refer to the address of the first element.
Pointers to Functions:
Void Pointers:
C and C++ include pointers of type void *, which can point to values of any type.
void * pointers are effectively generic pointers and are commonly used in
functions that operate on memory, as they allow passing pointers of any type.
Type checking is not an issue with void * pointers because these languages
disallow dereferencing them.
Reference Types
A reference type variable is similar to a pointer, with one important and fundamental difference: A
pointer refers to an address in memory, while a reference refers to an object or a value in memory.
A reference type variable in programming languages like C++ and Java is similar
to a pointer but with a fundamental difference: a pointer refers to an address in
memory, whereas a reference refers to an object or value in memory directly.
C++ includes a special kind of reference type used primarily for formal
parameters in function definitions.
C++ reference type variables are constant pointers that are implicitly
dereferenced, meaning they always refer to the object they are initialized with and
cannot be reassigned to refer to another object.
They are specified by preceding the variable name with an ampersand (&).
In C++
int result = 0;
C++ reference types enable two-way communication between caller and called
functions.
They allow passing values by reference, which means changes made to the
parameter inside the function affect the original value outside the function.
In Java
In Java, objects are implicitly deallocated by the garbage collector when they are
no longer referenced, eliminating the possibility of dangling references.
C# and Pointers:
C# includes both references and pointers, but the use of pointers is discouraged,
and any subprogram using pointers must be marked as "unsafe".
Objects pointed to by references are implicitly deallocated, but this is not true for
objects pointed to by pointers.
Object-Oriented Languages:
In languages like Smalltalk, Python, Ruby, and Lua, all variables are references,
and they are always implicitly dereferenced.
Direct access to the values of these variables is not possible; they can only be
accessed indirectly through references
In Python
Pointers and references are typically single values stored in memory cells, although early
microcomputers had two-part addresses (segment and offset).
In systems with two-part addresses, pointers and references were represented as pairs of
16-bit cells.
However, tombstones are costly in terms of both time and space due to additional
indirection and lack of deallocation.
Locks-and-Keys Approach:
Used in UW-Pascal, this approach represents pointer values as ordered pairs (key,
address). Heap-dynamic variables are associated with a lock value, and every
access checks if the key value matches the lock value.
Implicit Deallocation:
The best solution is implicit deallocation by the runtime system, removing the
responsibility from programmers. Systems like LISP, Java, and C# adopt this
approach for reference variables.
Heap Management:
Heap management involves allocating and deallocating memory on the heap, a complex
runtime process.
Two situations are discussed: allocation and deallocation in fixed-size units and in
variable-size segments.
Mark-Sweep Process:
A garbage collection algorithm where cells are marked as garbage, reachable cells
are marked as active, and unmarked cells are deallocated.
Variable-Size Cells:
Managing heaps with variable-size cells presents additional challenges such as
setting indicators, complex marking processes, and list maintenance overhead.