Week 7: Pointer (2)
Using the const
Qualifier with Pointers
const
Qualifier with PointersThe const
qualifier is a tool in C programming that signals to the compiler that the value of a given variable should remain unchanged, embodying the principle of least privilege. This practice aids in reducing debugging efforts, avoiding unintended consequences, and enhancing the overall robustness, modifiability, and maintainability of the program. Attempts to alter a const
-declared variable will lead to compiler errors, ensuring the integrity of the variable's value.
Historically, much of the early C codebase lacks the use of const
due to its unavailability, and even in contemporary code, its application is less frequent than ideal. This presents a considerable opportunity for refining existing C code through re-engineering to incorporate const
more effectively.
When passing data to a function via pointers, there are four principal methods, each offering different levels of access rights:
A non-constant pointer to non-constant data.
A constant pointer to non-constant data.
A non-constant pointer to constant data.
A constant pointer to constant data.
These combinations each grant distinct levels of data access and modification capabilities. The choice among them should be guided by the principle of least privilege, ensuring that a function receives only the necessary access to perform its duties, without excess. This approach helps in minimizing potential errors and enhancing code security and efficiency.
Using a non-constant pointer to non-constant data
Example: Converting a string to uppercase. A data can be modified through the dereferenced pointer, and the pointer can be modified to point to other data items.
Return value from toupper()
If an argument passed to toupper()
is
a lowercase character, the function returns its corresponding uppercase character
an uppercase character or a non-alphabetic character, the function the character itself
A constant pointer to non-constant data
An example of a non-constant pointer to constant data in C involves declaring a pointer that can change to point to different addresses, but the data it points to is immutable and cannot be modified through this pointer. This is useful when you want to iterate over an array or a string for reading purposes without accidentally modifying the data.
In this code:
The
printReverse
function takes a pointer to constant data (const char *str
) as its parameter. This means the function promises not to modify the data pointed to bystr
.Inside
printReverse
, a non-constant pointerptr
is initialized to point to the last character of the input string (excluding the null terminator).ptr
can change to point to different characters within the string (hence non-constant), but it points to constant data (const char
), ensuring the string data is not modified during the reversal process.The function iterates backward over the string, printing each character in reverse order. The pointer
ptr
is decremented to move backwards through the string.myString
is declared as an array of constant characters, which matches the parameter type ofprintReverse
. Even thoughmyString
is constant, you can still pass it to functions expecting a pointer to constant data, preserving the immutability contract.
Try!
If you add the following line in the while loop (before printf
)
you may see
How to solve it?
If you try to redefine ptr
as char
, not const char
:
you will get the waring
How to solve it?
sizeof
Operator
sizeof
OperatorThe sizeof
operator in C is a compile-time unary operator that returns the size, in bytes, of its operand, which can be either a variable, a literal, or a type (specified in parentheses). This size is determined based on the data type of the operand and the compiler being used, as different compilers or different target architectures may have varying sizes for the basic data types.
You may get
Arrays of Pointers
An array of pointers in C programming is a collection where each element of the array is a pointer. This structure combines the concepts of arrays and pointers, leveraging the ability of pointers to store memory addresses of other variables, including other arrays or strings.
Key characteristics and uses of arrays of pointers include:
Dynamic Data Structures: They are often used to create dynamic and complex data structures like an array of strings, where each pointer in the array points to the first character of a separate string. This is useful for handling a list of text items, such as names or words.
Efficiency: Arrays of pointers can be more efficient in terms of memory and processing power when dealing with an array of large structures or arrays. Instead of storing the actual data items directly in the array, storing pointers to the data can save space and allow for quicker modification and access, as only the addresses are moved or changed, not the data itself.
Functionality: They allow for the creation of non-contiguous data structures and can facilitate the handling of multidimensional arrays in a more flexible manner. For example, each pointer in the array can point to arrays of different sizes, enabling the construction of jagged arrays.
Memory Allocation: Arrays of pointers are crucial for dynamic memory allocation scenarios, where the size of data structures isn't known at compile time and can grow or shrink at runtime. They are used to point to blocks of memory allocated on the heap.
Here's a brief example to illustrate an array of pointers:
Every pointer in the array targets the initial character of its respective string. Therefore, despite the fixed size of a char *
array, it has the flexibility to reference character strings of varying lengths.
What if we use two-dimensional array?
A two-dimensional array could be used as an alternative to an array of pointers for storing strings like "Hearts", "Diamonds", "Clubs", and "Spades". In this scenario, each string would be stored in a separate row of the array. However, this approach can lead to inefficient memory usage.
The inefficiency stems from the requirement that the two-dimensional array must be declared with fixed dimensions. Specifically, the column size must be large enough to accommodate the longest string, including the null terminator character. This means that for shorter strings, the remaining space in their respective rows is wasted. For example, if the longest string is "Diamonds" with 8 characters (plus the null terminator, making 9), then each row in the array must have enough space for 9 characters, even though "Hearts", "Clubs", and "Spades" do not require that much space.
To compare the memory usage between using an array of pointers and a two-dimensional array for storing strings like "Hearts", "Diamonds", "Clubs", and "Spades", let's break down the memory requirements for both cases. We'll assume a scenario where the system uses a 4-byte pointer (common in 32-bit systems, though 64-bit systems typically use 8-byte pointers).
Array of Pointers
In the array of pointers case:
Each pointer consumes 4 bytes.
There are 4 pointers for the 4 strings, totaling
4 pointers * 4 bytes/pointer = 16 bytes
.The strings themselves are stored in separate memory locations. Assuming ASCII encoding (1 byte per character) and including the null terminator for each string:
"Hearts" = 7 characters = 7 bytes
"Diamonds" = 9 characters = 9 bytes
"Clubs" = 6 characters = 6 bytes
"Spades" = 7 characters = 7 bytes
Total string memory =
7 + 9 + 6 + 7 = 29 bytes
Overall total memory usage =
16 bytes (for pointers) + 29 bytes (for strings) = 45 bytes
Two-Dimensional Array
In the two-dimensional array case:
The array size is determined by the longest string, "Diamonds", which has 9 characters including the null terminator.
Each row must accommodate 9 characters, regardless of the actual string length.
With 4 strings, the array size is
4 rows * 9 characters/row = 36 characters
.Each character is 1 byte, so the total memory usage is
36 characters * 1 byte/character = 36 bytes
.
Comparison
Array of Pointers: 45 bytes total (16 bytes for pointers + 29 bytes for actual strings).
Two-Dimensional Array: 36 bytes total, allocated as a contiguous block.
While the two-dimensional array seems more memory-efficient in this particular case, it's important to remember that the array of pointers approach offers greater flexibility and can be more memory-efficient when dealing with a large number of strings or significantly varying string lengths, as it avoids the over-allocation for shorter strings. The array of pointers approach also allows strings to be dynamically modified or replaced without reallocating the entire array, offering advantages in scenarios where memory efficiency and flexibility are critical.
Function Pointers
Function pointers are powerful tools that allow the dynamic invocation of functions, enabling programming techniques such as callback functions, dynamic dispatch, and event-driven programming. A function pointer holds the address of a function that can be called later in the program. This capability provides flexibility in code structure and logic, allowing functions to be passed as arguments to other functions, returned from functions, stored in arrays, or assigned to variables.
Using function pointers to create a menu-driven system
Classwork - Week 7
Exercise - 1
Using the function pointer technique, create a menu-driven program to calculate circle circumference, circle area, or sphere volume. The program should allow the user to choose what to calculate. It should then input a radius from the user, perform the appropriate calculation, and display the result. Each function should display messages indicating which calculation was performed, the value of the radius, and the result of the calculation.
Exercise - 2
Study the maze-solving algorithm called the right-hand rule, summarize, and visualize on the answer sheet. Given that the entrance is at [0][1]
, what will be the exit?
Last updated