Lab 4: Pointers, Aliasing, and In-place Reversal
In this lab, you will investigate how pointers refer to objects, test a swap function that appears to work, and build a function that reverses part of an array in place. Your explanations and test cases matter as much as your final output.
1. Objectives and Lab Workflow
- Distinguish a pointer variable from the object it points to.
- Explain aliasing: two or more pointers referring to the same object.
- Use pointer parameters to modify the caller’s data.
- Pass an array segment to a function using a starting address and a length.
- Distinguish address equality, equal contents, and permission to modify data.
Use C throughout. This lab does not require dynamic memory allocation, file input, arrays of pointers, or pointers to pointers. For each investigation, predict first, run or debug second, and explain any difference. Keep your original predictions in your notes.
| Activity | Time |
|---|---|
| Trace pointers and shared access | 15 minutes |
| Investigate and repair a swap function | 25 minutes |
| Implement and test partial array reversal | 40 minutes |
Discuss strings and const | 20 minutes |
| Check results and summarize findings | 10 minutes |
Working with addresses: The lecture uses a 32-bit model for address exercises. Your actual environment may use a different pointer size. Measure it with sizeof; do not assume that every machine uses four-byte pointers. Print object-pointer addresses with %p and a conversion to void *, for example:
printf("p = %p, sizeof p = %zu\n", (void *)p, sizeof(p));
Exact address values can differ between runs. Focus on which object an address identifies.
2. Investigation A: Two Pointers, One Object
Place this fragment in a small C program. Before running it, predict the array contents and the element to which each pointer refers after every statement.
int a[] = {10, 20, 30, 40};
int *p = a;
int *q = p;
*p = 11;
p++;
*q = *p + 1;
q = p;
(*q)++;
Copy and complete the following table. Identify pointer targets as a[0], a[1], and so on, rather than guessing numerical addresses.
| State | Array contents | p points to | q points to |
|---|---|---|---|
| After initialization | 10, 20, 30, 40 | a[0] | a[0] |
After *p = 11; | |||
After p++; | |||
After *q = *p + 1; | |||
After q = p; | |||
After (*q)++; |
Use breakpoints and single stepping to inspect a, p, q, *p, and *q. Alternatively, print the state after each statement.
Discuss:
- When
pmoves, doesqmove too? Explain using two pointer variables and their targets. - Does
q = pcopy an integer from the array, or does it copy an address? - How do
(*q)++andq++differ?
Checkpoint: Draw the pointer relationships after p++ and after q = p. Be ready to explain both drawings.
3. Investigation B: Does This Swap Always Work?
The following function uses arithmetic to exchange two integers. Treat it as code under review, not as a trusted helper.
void swap_arithmetic(int *a, int *b)
{
*a = *a + *b;
*b = *a - *b;
*a = *a - *b;
}
3.1 Construct Counterexamples
Start each case with freshly initialized variables. Predict the result before testing.
| Case | Initial state and call | Question |
|---|---|---|
| Different values | x = 3; y = 8;swap_arithmetic(&x, &y); | Does the usual case work? |
| Equal values, different objects | x = 5; y = 5;swap_arithmetic(&x, &y); | Does equal content cause a problem? |
| The same object twice | x = 5;swap_arithmetic(&x, &x); | What changes when both pointers identify one object? |
| Large values | x = INT_MAX; y = 1;swap_arithmetic(&x, &y); | Can every intermediate result be represented as an int? |
For the third case, step through each assignment and record the value of x. For the fourth case, include <limits.h> and reason about the range before execution. Signed integer overflow is undefined behavior in C: there is no required numerical result. A value observed in one run is not a guarantee, and running the overflowing case is not required.
3.2 Repair the Function
Implement:
void swap_int(int *a, int *b);
Both arguments must point to valid, writable int objects. Your function must exchange their values when the addresses differ and preserve the value when both arguments point to the same object. Use a temporary integer; do not rely on arithmetic swapping.
Now inspect this second attempted solution:
void swap_attempt(int *a, int *b)
{
int *temp = a;
a = b;
b = temp;
}
Discuss: Does this function exchange the caller’s integers? Which objects do its assignments actually modify? Explain why C’s pass-by-value rule still applies when parameters are pointers.
Checkpoint: Show a failing test for swap_arithmetic() and the corresponding successful test for swap_int(). Explain the difference between equal values and equal addresses.
4. Main Task: Reverse Part of an Array
Implement a reusable function that reverses a contiguous segment in place:
void reverse_segment(int *first, int count);
first identifies the beginning of the segment, and count is its number of elements. The function does not receive the original array’s total length or the segment’s starting index.
4.1 Function Contract
countis nonnegative. For a nonempty segment, the caller provides a valid range ofcountwritable integers.- For this lab, an empty segment may start at any element or at the position just past the end of the array. Do not dereference that position. Null-pointer handling is not required.
- For
countequal to 0 or 1, leave the data unchanged. - Use your repaired
swap_int(). Do not allocate a second array. - Do not modify any element outside the selected segment.
You may control the loop using integer positions and construct addresses for the swap calls. A two-pointer implementation is an optional alternative. Handle an empty segment before constructing a pointer to its last element: first + count - 1 is not a valid general way to initialize an empty range.
4.2 Input and Expected Behavior
Use a fixed capacity of 20 integers. Read n, then n array elements, then a zero-based starting index and a count. For example:
7 10 20 30 40 50 60 70 2 3
Expected output:
Before: 10 20 30 40 50 60 70 After: 10 20 50 40 30 60 70
The call for this example is reverse_segment(a + 2, 3). The two elements before the segment and the two after it must remain unchanged.
4.3 Starter Program
The input, output, and range checks are provided so that you can focus on the pointer functions. Complete the two function bodies. The initial template compiles but does not yet perform any reversal.
#include <stdio.h>
#define CAPACITY 20
void swap_int(int *a, int *b)
{
/* TODO: Exchange the pointed-to values using a temporary int. */
}
void reverse_segment(int *first, int count)
{
/* TODO: Reverse this segment using swap_int(). */
}
void print_array(const int *a, int n)
{
for (int i = 0; i < n; i++) {
printf("%s%d", i == 0 ? "" : " ", a[i]);
}
printf("\n");
}
int main(void)
{
int a[CAPACITY];
int n, start, count;
if (scanf("%d", &n) != 1 || n < 1 || n > CAPACITY) {
printf("Invalid array length.\n");
return 1;
}
for (int i = 0; i < n; i++) {
if (scanf("%d", &a[i]) != 1) {
printf("Invalid array element.\n");
return 1;
}
}
if (scanf("%d %d", &start, &count) != 2) {
printf("Invalid segment input.\n");
return 1;
}
if (start < 0 || start > n || count < 0 || count > n - start) {
printf("Segment is outside the array.\n");
return 1;
}
printf("Before: ");
print_array(a, n);
reverse_segment(a + start, count);
printf("After: ");
print_array(a, n);
return 0;
}
Supply input integers representable as int. No file input is needed. An array name converts to a pointer to its first element in most expressions, but the array itself is not a pointer variable.
4.4 Required Tests and Discussion
| Test | What to check |
|---|---|
| Reverse the whole array | Both odd and even lengths work. |
| Reverse an interior segment | Elements outside the segment remain unchanged. |
| Reverse zero elements | No changes or invalid accesses; also try start == n. |
| Reverse one element | The element is preserved. |
| Include repeated values | No elements are lost or altered. |
| Reverse the same segment twice | The original array is restored. |
| Request an invalid segment | The supplied input checks reject it. |
Investigate the middle element: In an odd-length segment, what if your loop calls the swap function once more when its two positions meet? Compare the repaired swap with the arithmetic swap, using small values to avoid overflow. Explain how a helper-function defect can affect a larger algorithm.
Discuss: How can this function modify part of the caller’s array without knowing the complete array length? Why must the caller supply a valid segment? Does reversing twice prove correctness by itself, or are other tests still necessary?
Checkpoint: Demonstrate the sample, an empty segment, and a one-element segment. Show that an interior reversal preserves the surrounding elements.
5. Investigation C: Equal Contents, Equal Addresses, and const
Use this setup:
char s1[] = "cat";
char s2[] = "cat";
char *p = s1;
const char *q = s1;
char *const r = s1;
Predict and test s1 == s2 and p == q. These expressions compare addresses, not string contents. Then modify the first character through p and observe what you read through q.
Treat each statement below as an independent case, starting from the original setup. Predict whether it is allowed, and identify whether it would change an address or a character. Compile one case at a time, recording any diagnostic; do not cast away const.
| Statement | Allowed? | What would it change? |
|---|---|---|
p = s2; | ||
q = s2; | ||
*q = 'C'; | ||
r = s2; | ||
*r = 'C'; |
Discuss: Does const char *q mean that the underlying character array can never change, or that characters cannot be changed through this pointer? How is it different from char *const r?
Finally, compare the following pointers:
const char *u = "cat";
const char *v = "cat";
printf("%d\n", u == v);
Record the result, but do not treat it as a universal answer. Identical string literals may share storage or occupy different storage. Use content comparison, such as strcmp() from <string.h>, when you need to compare strings. Do not attempt to modify a string literal: that has undefined behavior, not a guaranteed runtime error.
6. Optional Extension: Mirror Two Rows
Use a small two-dimensional array:
int image[2][3] = {
{11, 12, 13},
{21, 22, 23}
};
Predict how far image + 1 advances compared with image[0] + 1. Print their addresses using %p and (void *), and relate the results to the types of the pointed-to objects. Use %d when printing an integer element.
Then reuse your function:
reverse_segment(image[0], 3);
reverse_segment(image[1], 3);
The expected rows are 13 12 11 and 23 22 21. Explain why you can reuse a one-dimensional segment operation on each row. Keep each reversal within its row; a two-dimensional array is not an int **.
7. What to Submit
- C source: your completed swap and segment-reversal program.
- Test notes: your pointer-trace table, a counterexample to the arithmetic swap, the required reversal tests with expected and observed results, and the
conststatement classifications. Note any prediction you revised. - Three short explanations:
- Why can passing the same address twice break the arithmetic swap even when equal values in two different objects do not?
- Why does copying or exchanging local pointer variables not copy or exchange the pointed-to data?
- Why can data observed through a pointer to
constchange in this lab even though writing through that pointer is disallowed?
Optional extension work is welcome but is not needed to complete the core lab. Be prepared to explain your code and the pointer relationships during the lab check.