C++ Pointers Made by: Turki Alamri Adel AlHarbi Fawaz Alanzi Aouf Alsayed Mouthanna Alsharif under the supervision of Dr: Hassan Aleraqi Keywords: - Pointers Raw Pointers Const and Volatile Pointers New and Delete Operators Smart Pointers Function Pointers Abstract: When we declare a variable in C++, a specific location in memory is assigned to it to store a value in this variable. This location is called the memory address of the variable. Pointers in C++ are special variables that store the memory address of other variables. Pointers add more features and flexibility to the C++ programming language. Introduction: C++ is one of the simplest and most common programing languages that provides the programmer with a good using experience, it has many tools to choose from and one of them being pointers. Pointers are used usually in both C and C++ for 3 main purposes: • passing functions to other functions • allocating new objects on the heap, • iterating over elements in data structures. Raw pointers: The pointer whose lifetime isn't controlled by an encapsulating object is raw pointer, such as a smart pointer. A raw pointer can be assigned the address of another variable which is non-pointer, or it can be assigned a value of nullptr. When the pointer hasn't been assigned a value, it contains random data. When allocating an object on the heap in memory by a program, the heap receives the address of that object in the form of a pointer. Such pointers are called owning pointers. An owning pointer (or a copy of it) must be used to explicitly free the heap-allocated object when it's no longer needed. When fail to free memory, it results in a memory leak and makes that memory location unavailable to any other program on the machine. allocated memory which uses new must be freed by using delete (or delete[]). When pointer is not declared as const, it can be incremented or decremented to point at another location in memory. This operation is called pointer arithmetic. It's used in C-style programming to iterate over elements in data structures. Const and volatile pointers: Const and volatile pointers have specific uses in C++. The const keyword is used to specify that a pointer cannot be modified after initialization. This means that the value it points to is protected from any modifications. On the other hand, the volatile keyword is used to indicate that the value associated with a name can be modified by actions outside of the user application. This is particularly useful for objects in shared memory that can be accessed by multiple processes or for global data areas used in communication with interrupt service routines. When a name is declared as volatile, the compiler reloads the value from memory every time it is accessed. This prevents certain optimizations, but it ensures predictable program performance when the state of an object can change unexpectedly. To declare a const or volatile object pointed to by a pointer, the syntax is as follows: To declare a const or volatile value of the pointer itself (the address stored in the pointer), the syntax is: new and delete operators: The new operator: The compiler translates a statement like this one into a call to the function operator new: If the request is for zero bytes of storage, operator new returns a pointer to a distinct object. That is, repeated calls to operator new return different pointers. If there's insufficient memory for the allocation request, operator new throws a std::bad_alloc exception. Or it returns nullptr if you've used the placement form new(std::nothrow), or if you've linked in nonthrowing operator new support. Scope for operator new functions The global operator new function is called when the new operator is used to allocate objects of built-in types, objects of class type that don't contain user-defined operator new functions, and arrays of any type. When the new operator is used to allocate objects of a class type where an operator new is defined, that class's operator new is called. The delete operator: The delete operator calls the operator delete function, which frees memory back to the available pool. Using the delete operator also causes the class destructor (if one exists) to be called. There are global and class-scoped operator delete functions. Two forms exist for the global operator delete and class-member operator delete functions: The first form takes a single argument of type void *, which contains a pointer to the object to deallocate. The second form, sized deallocation, takes two arguments: the first is a pointer to the memory block to deallocate, and the second is the number of bytes to deallocate. The return type of both forms is void (operator delete can't return a value). The intent of the second form is to speed up searching for the correct size category of the object to delete. This information often isn't stored near the allocation itself, and is likely uncached. The second form is useful when an operator delete function from a base class is used to delete an object of a derived class. Smart pointers (Modern C++): In modern C++ programming, the Standard Library includes smart pointers, which are used to help ensure that programs are free of memory and resource leaks and are exception-safe. Uses for smart pointers: Smart pointers are defined in the std namespace in the header file. They are important to the RAII or Resource Acquisition Is Initialization programming idiom. The main goal of this idiom is to make sure that resource acquisition happens at the same time that the object is initialized, so that all resources for the object is made ready in one line of code. In a lot of cases, when you initialize a raw pointer or resource handle to point to an actual resource, pass the pointer to a smart pointer immediately. In modern C++, raw pointers are only used in small code blocks of limited scope, loops, or helper functions where performance is critical and there is no chance of confusion about ownership. A smart pointer is a class template that you declare on the stack and then initialize by using a raw pointer that points to a heap allocated object. After the smart pointer is initialized, it basically owns the raw pointer. This means the smart pointer is the one responsible for deleting the memory that the raw pointer specifies. Access the encapsulated pointer by using the familiar pointer operators, -> and *, which the smart pointer class overloads to return the encapsulated raw pointer. Function Pointer in C++: The function pointer is a pointer which main use is to point functions. Its main purpose is to store the address of a function. We can call the function by using the function pointer (sometimes referred as funcptr) or we can also pass the pointer to another function as a parameter. They are mainly useful for event driven applications and callbacks and even for storing the functions in arrays. The computer only understands the low-level language, i.e., binary form. The program we write in C++ is always in high-level language, so to convert the program into binary form, we use compiler. The compiler source code into an executable file then the executable file gets stored in the RAM. The CPU starts the execution from the int main() method and it reads the copy in RAM but not the original file. All the functions and machine code instructions are data and these data is a bunch of bytes and all these bytes have some address in RAM. The function pointer contains the RAM address of the first instruction of a function. Syntax for Declaration: here in the following is the syntax for the declaration of a function pointer: Here in this syntax, we supply the return type, and then the name of the pointer, i.e., FuncPtr which is surrounded by the brackets and preceded by the pointer symbol, i.e., (*). After this we have fill the parameter list (int,int). The above function pointer can point to any function which takes two integer parameters and returns integer type value. Therefore, the name of the function itself without any brackets or parameters means the address of a function. We can still use an alternative way to print the address of a function, i.e., &main. Address of a function: We can get the address of a function easily by mentioning the name of the function without having to call the function, An example. In the above program, we are displaying the address of a main() function. To print the address of a main() function, we have just mentioned the name of the function, there is no bracket not parameters. Therefore, the name of the function by itself without any brackets or parameters means the address of a function. We can use the alternate way to print the address of a function, i.e., &main. Calling a function indirectly : We can call the function with the help of a function pointer by simply using the name of the function pointer. An example. In the above program, we declare the function pointer, i.e., int (*funcptr)(int,int) and then we store the address of add() function in funcptr. This implies that funcptr contains the address of add() function. Now, we can call the add() function by using funcptr. The statement funcptr(5,5) calls the add() function, and the result of add() function gets stored in sum variable. Passing a function pointer as a parameter: The function pointer can be passed as a parameter to another function, An example. In the above code, the func2() function takes the function pointer as a parameter. The main() method calls the func2() function in which the address of func1() is passed. In this way, the func2() function is calling the func1() indirectly. Conclusion: Some Common Mistakes to Avoid with Pointers in C++: here are a few important common mistakes that should be avoided while using pointers. • Not Initializing Pointers: When you declare a pointer variable, it does not point to a specific memory address automatically. • Dangling Pointers: A dangling pointer is a pointer that points to a memory location that has been freed or erased. This can lead the program to undefined behavior or even a crash. • Forgetting to Dereference Pointers: it means accessing the value stored at the memory address pointed to by the pointer. If you forgot to dereference a pointer it can result in errors or unexpected behavior. • Not Checking for NULL Pointers: NULL pointers are pointers that do not point to any valid memory address. If you try to access the value stored at a NULL pointer, you will get a segmentation fault(access violation). • Memory Leaks: Memory leaks occur when you allocate memory dynamically using functions like malloc() or new but fail to deallocate it when it is no longer needed. This can lead to a shortage of memory, causing the program to crash. Conclusion (Continued) : In summary, pointers are a powerful tool in C++ programming that allows for dynamic memory allocation and manipulation of data structures. While they can be tricky to use correctly, understanding how pointers work and being extra careful with memory management can lead to efficient and effective code. It is important to remember to always initialize pointers, avoid dangling pointers, and properly deallocate memory to avoid memory leaks. With proper usage and care, pointers can be an invaluable asset to any C++ programmer. Resources: C++ Pointers by Microsoft [2021] Pointers in C++ by Scaler Topics [2023] Pointers in C++ by Shivani Goyal [2020] Function Pointers in C++ by java point [2021]