answersLogoWhite

0

The purpose of a pointer is to store a memory address and to allow indirect access to the value stored in that memory address. The act of indirectly accessing memory through a pointer is known as dereferencing. Thus a pointer is said to hold a reference; it "points" to the reference. However, a reference and a pointer are not the same thing. A reference is an alias, an alternate name by which we can refer to an existing object. An object can have many aliases, but aliases does not require any memory over and above that of the object itself, unlike a pointer which does require memory.


The address of a reference is always the address of the object being referred to (just as Bill and Billy are different ways of referencing someone with the name William). However the address of a pointer is always the address of the pointer itself; it is a variable just like any other. The value of a pointer is the address that is stored in the pointer; the address of the object being referenced by the pointer. The dereferenced value of the pointer is the value stored at that memory address; the value of the referenced object itself.


In C++, we can enumerate all the properties of a variable, a reference to that variable and a pointer to that variable like so:


int i = 42; // instantiate an integer with the value 42

std::cout<<"Address of integer: 0x"<<&i<

std::cout<<"Value of integer: "<

int& r = i; // refer to i

std::cout<<"Address of reference: 0x"<<&r<

std::cout<<"Value of reference: "<

int* p = &i; // point to i

std::cout<<"Address of pointer: 0x"<<&p<

std::cout<<"Value of pointer: 0x"<

std::cout<<"Dereferenced value of pointer: "<<*p<


The output of this code would be something like this:


Address of integer: 0x00FBFBAC

Value of integer: 42

Address of reference: 0x00FBFBAC

Value of reference: 42

Address of pointer: 0x00FBFB94

Value of pointer: 0x00FBFBAC

Dereferenced value of pointer: 42


The actual memory addresses may be different on your system. However, note that the address of the reference is the same as the address of the integer, the object being referenced. Thus r and i are aliases, different names that refer to the same object. As such they have the same value. After all, the are one and the same object. The address of the pointer, however, is a different address. This proves that pointers are variables like any other; they have an address all of their own. Note also that the value of the pointer is the address of i (and, by extension, the address of r). The third property, the dereferenced value, is unique to pointer types. This returns the value of the object that is referenced by the pointer value.


This is obviously a trivial example and doesn't cover the full range of a pointer's potential, however it's important to understand the difference between a pointer and a reference. Not all languages that support pointers also support references like C++ does, thus you will often see the terms pointer and reference being used interchangeably. C is a typical example because it has no concept of a dedicated reference types but it does have pointer types. To be crystal clear, a reference is the value stored in a pointer (the address being referred to), it is not the pointer itself.


Aside from the semantic difference, another difference between a pointer and a reference is that a reference must always refer to something whereas a pointer need not. When a pointer doesn't refer to anything in particular, it is assigned address zero. Memory address zero is a reserved address which cannot be dereferenced and a pointer that holds the value zero is said to be a null pointer. The importance of the null pointer will become clear.


Now that we have an understanding of what pointers can actually do, what are the advantages and disadvantages?


The main advantage is that we can use pointers to pass objects into functions by reference. Languages like C and C++ use pass by value semantics by default which means that when we pass an object to a function, the object is copied; only the value of the object is passed, not the object itself. If we want the function to operate upon the object itself, we must pass the object by reference, not by value. C has no concept of a reference, let alone pass by reference, but if we pass a pointer to the object, the pointer is passed by value, and since the value is a memory address, it is the same as passing the object itself, by reference.


This is both an advantage and a disadvantage. It is advantageous in that we can pass objects by reference, but it is disadvantageous because we must be certain the pointer is non-null before we can actually operate upon it. This means that every function that accepts a pointer as an argument will have to perform the exact same test upon that pointer; does the pointer actually point at something valid? In C++ we don't have this problem because we can simply uses a reference instead of a pointer argument. References can never be null; they must always refer to a valid object thus there's no need to test the reference.


However, sometimes we want to pass an object as an optional parameter rather than a required parameter, so passing a null pointer would be acceptable. We still have to perform the test for non-null, but there's no way to pass an object as an optional parameter other than by instantiating a default object which can be considerably less efficient than simply testing if a pointer is zero or not.


Another advantage of a pointer is that when we wish to allocate memory dynamically, we must keep track of the start address of the allocation. The operating system's memory manager knows exactly how much memory is allocated to each address, so when we're finished with a block of memory, all we really need is the start address and the memory manager takes care of the rest, making that block of memory available to any thread or process that requires it. However, sometimes the memory manager may report that there is insufficient memory to meet an allocation, so no memory can be allocated. That's not a problem because if an allocation cannot be met, the operating system returns address zero as the start address. so each time we make an allocation, we simply store the value returned by the allocator in a pointer. If it is null, there is no memory available. We cannot use references for this purpose because references cannot be null, thus a pointer is clearly advantageous here.


In languages that do not support pointers, such as Java, memory is allocated to the Java virtual machine (using internal pointers of course) and the JVM provides the memory management required by its programs. There's still the chance of running out of memory, but the onus of responsibility shifts to the JVM's memory manager, away from the programmer, which leads us to one of the biggest disadvantages of pointers.


If the programmer fails to keep track of memory allocations, the program will inevitably leak memory (known as a resource leak). That is, if he allows all pointers to a memory resource to fall from scope, there's no way to recover the start address and thus no way to release the memory to the system. The problem with pointers is that there is no concept of resource ownership. A pointer simply holds a memory address, but there's no way to determine if that pointer actually owns the memory it is pointing at or if it simply referring to memory that is shared. In truth, all memory is shared and no pointer can physically own the memory it points at. We can easily end up with hundreds of pointers to the same object. Therefore the programmer needs to take extra care to ensure that whenever memory is released back to the system, all pointers to that memory must be immediately nullified. This is easier said than done, because not all pointers may be in scope at the point the memory is released, thus they will still be pointing at what they believe is valid memory. Any attempt to dereference memory that has already been released will result in undefined behaviour because that memory may have already been reallocated so it may not even belong to our program let alone still be in a valid state. And if we attempt to release the same memory twice, undefined behaviour is the least of our worries. So it's important that the programmer be aware of memory ownership, even though there's no way to actually define that ownership with a pointer.


In C++ this is less of a problem because we can encapsulate resources in special classes known as resource handles. Technically, a resource handle is a smart pointer. It behaves exactly like an ordinary pointer would, except that when the smart pointer falls from scope it automatically releases its resource. Thus ownership of the resource falls to the resource handle itself. A resource handle can also pass ownership to another resource, thus ownership of a resource becomes more explicit. If we wish to share ownership, we can use specialised resources handles that deal with shared resources through reference counting. The count is increment each time we add a new handle to a resource and decremented each time we release a handle to that resource. When the count reaches zero, the resource is released when the one and only remaining handle falls from scope. We can also use weak references to shared memory. Weak references are more like ordinary pointers except they cannot release resources, but when a shared resource falls from scope, the weak references are automatically nullified. In languages that do not support resource handles, the onus is entirely upon the programmer to manage their own resources through "raw" pointers, which is a distinct disadvantage and is often less efficient because of the complexity of defining resource ownership in a system where ownership is not a native concept.


When used properly and in an organised manner, pointers have many advantages and can lead to more efficient code. For instance, being able to navigate your way through consecutive memory addresses is more efficient than navigating your way through a series of named variables. This is precisely how arrays work, even in languages that do not support native pointers. An array is simply a contiguous block of allocated memory divided into one or more elements of equal size. Knowing the start address of the allocation and the size of each element, we can refer to any one element in the array as an offset address from the start of the array. Of course most languages will provide a more convenient array index operator (technically a suffix operator) that allows use to access individual elements using a zero-based index value ([0], [1], [2], [3], etc). However, Behind the Scenes, we're actually doing pointer arithmetic. The 3rd element in an array can be found at index [2], but what this really means is that the element resides at the 2nd offset from the start of the array. Thus if an element is 4 bytes in length, the 3rd element will be found at the address that is 2 * 4 bytes from the start of the array. Simple pointer arithmetic like this is a constant time operation, thus we gain constant time random access to any element in the array, whether it is the first element, the last element, or any element in between.


Arrays are by far the most compact and efficient method of storing data elements of the same length and type. However arrays are not ideally suited to data that is variable length. This is because every time we add a new element to an array we must reallocate the array, which may mean copying the array to new memory. There are ways to minimise the need for reallocations, such as allocating more memory than is actually required, however this undermines the efficiency by allocating memory that might not be needed. Moreover, removing elements is problematic because we can only efficiently remove the last element in an array. In order to keep track of unused elements, the unused elements must reside at the end of the array so we only need to maintain a count of those elements, not where each one is. If we remove any other element besides the last, we must move all the elements that follow it in order to close the gap and move the gap into the unused block.


To maintain efficiency we can use other structures that do not require contiguous memory. For instance, rather than allocating a single contiguous block of memory we can divide the memory into separate blocks of equal size. Thus when we run out of space, we simply allocate a new block, leaving the original blocks in place; no need to reallocate them. This is achieved by creating an array of pointers, where each pointer refers to a separate block. We use additional memory to maintain this array, but we now have the advantage that an array can grow and shrink more efficiently without having to reallocate the existing blocks. In effect, we're actually creating an array of arrays, otherwise known as a multi-dimensional array. However, since the controlling array contains pointers and the value of a pointer is a memory address, we still have constant-time random access to any element, because when we refer to a pointer by its index within the pointer array, the return value is another array, thus we can use a two-dimensional suffix to access an individual element, just as if we'd allocated the array as a single contiguous block. This would not be possible without pointers.

Moreover, we can also more easily remove and insert elements from either end of the array, but not from the middle.


If we need to add or remove lots of elements in the middle of a structure, then we need a more complex structure known as a linked list. Each element in a linked list is a node and each node points to the next node. The last node, the tail node, simply points to null and we only need to keep track of the first node, known as the head node. We lose constant time random access as well as bidirectional access because, to locate any node, we must traverse the pointers from the head node. However, since the structure is now encapsulated by the nodes themselves, the nodes can physically reside anywhere in memory and we can insert and extract nodes simply by adjusting the internal pointers between the affected nodes. For instance, if we have nodes x, y and z where x points to y and y points to z, in order to remove y we first navigate our way from the head until we reach x (because it points to y, the node we wish to delete). We then copy the value of y's pointer (which holds the address of z). We can then release y from memory and finally replace x's pointer with the value we copied. So x now points to z.


With a linked list, all insertions must be done at the head because that's the only node we have constant time access to. However, if we point the tail back at the head instead of null we create a circular linked list. Since the tail now points at the head, if we keep track of the tail rather than the head then we get constant time access to both the head and the tail. Now we can insert at either end of the list in constant time. Although lists do not allow constant time access to any element in the lists, if we need to perform a lot of insertions in the middle of the list, it is more efficient than using an array because we only need to maintain the internal links of the affected nodes, we don't have to reallocate or move elements around in order to make room for insertions or cater for extractions. The pointers consume additional memory of course (one word per element) however it is more efficient overall. If we require bidirectional traversal, then we can add a second pointer to each node so that they point to the previous element as well as the next.


The concept of a bidirectional node can be extended so that rather than simply pointing forwards and backwards through a list, we can point left and right to construct binary trees, where every node has up to 2 child nodes. These are particularly useful when we wish to sort data. Each node represents the root of its own subtree such that the left node points to a subtree with values that are less than this node's value while the right node points to a subtree with values that are not less than this node's value. Thus when we insert values, we simply navigate from the root of the tree, traversing left or right according to the value in the current node until we reach a null pointer in the required direction. We then insert a new node at that position. If there is no root, the value simply becomes the root node. New nodes always have null child pointers.


Of course a binary tree can easily end up unbalanced such that there are more nodes on one side of the root than the other. To improve efficiency, we can adjust the internal pointers upon each insertion to maintain balance, making it more efficient to locate a value regardless of which side of the subtree it is situated. To enable this balancing, we need to insert sentinel nodes, which uses additional memory, but improves the overall efficiency of the structure. These trees are typically known as self-balancing trees or red/black trees.


All these non-contiguous structures (and more besides) share a common aspect in that they all use pointers to iterate through the sequence. In C++ we can take advantage of this and create an iterator class. This behaves much like a pointer would (and is not unlike a smart pointer in that respect), but we can overload the increment and decrement operators to make traversal of these structures more intuitive. Normally, when we increment a pointer, the value of a pointer is incremented or decremented by the number of bytes dictated by its type. But an iterator can be forced to replace its value with that of another pointer. Thus if we have an iterator to the start of a sequence and we increment that iterator, we automatically traverse to the next element in the sequence.


Pointers clearly have many advantages, but not all languages implement them. This is primarily because pointers are low-level and require a lot of knowledge and skill to use them properly and safely. High-level languages do implement pointers but they hide the low-level implementation details within objects including resource handles, smart pointers, iterators and so on. Thus complex structures like lists and binary trees are still possible in high level languages without using raw pointers but, behind the scenes, there's still a lot of raw pointer arithmetic going on, it's simply hidden from view and that much harder to reach them (if at all). But knowing what's going on behind the scenes inevitably leads to more efficient code and who knows...? It may even spark an interest in getting to grips with low-level coding. If you want speed and efficiency, pointers are the fundamental means of achieving it.

User Avatar

Wiki User

10y ago

What else can I help you with?

Related Questions

What is the Advantages and disadvantages of server side includes?

c


What are the advantages and disadvantages of using a DVD storage device?

c


What are the advantages of pointer?

Pointers in C are advantageous because they allow for referencing of data without actual manipulation of the data. It is also helpful because it is not necessary to recreate an instantiation of the data locally, but merely reference the pointer.


Advantages of pointer in c?

pointer in C have following advantages- 1.Pointer is a very useful concept for creating the important C data structures i.e. linked list, stack, queues and trees, which are very powerful in certain situations. 2.Pointers are very useful when we have to reflect more than one variable change in the calling function after call takes place i.e. though we can not return more than one value from a called function but we can always pass references (pointer variables) to the variables in calling function as parameters to the function.So all the manipulation using these pointer variables will be reflected in called funtion. 3.Pointer provides a lower level view of memory, it adds to our understanding of the things going on in your computer memory.


How can you offset a pointer in C?

Increment or decrement the pointer by the required offset.


What is pointer to a member in objective c?

It is a pointer that points to a member of a structure.


Pointer arithemetic in C?

no


What is the purpose of pointer in C?

the purpose of pointer in c for saving the memory space.,and reduce the length and complexity of the program


WHAT IS POINTER TO POINTER IN C POINTER?

Pointer in C is Memory Reference. It stores memory address of any variable, constant, function or something you later use in your programming. Pointer basically used to ease the referencing of variables and others or in polymorphism and inheritance.


What is Dazzling Pointer in c plus plus?

The pointer that points to a block of memory that does not exist is called a dazzling pointer or wild pointer


What is stream pointer in c?

C does not have stream pointers.


Define pointer in C?

Pointer is a variable, A variable that stores the address of another variable. Size of a pointer is 2 bytes.