🔬 Lab
CS 240 Lab 6
Extra Pointers Exercises
CS 240 Lab 6 Extras
This notebook contains some extra exercises if you want more practice with pointers and pointer programming.
Extra Exercises: More Pointers Practice
If you want more practice understanding pointers, pointer math, and related types, deducing the values of various expressions in the table below.
You’ll be filling in values based on this code:
int *start = (int*) 0xa0000;
int *end = (int*) 0xa0010;
char *startStr = (char*) start;
char *endStr = (char*) end;
// Line 1
startStr[0] = 2;
startStr[1] = 1;
startStr[2] = 0;
startStr[3] = 0;
// Line 2
startStr[2] = 1;
*start = 515;
// Line 3For each line in the table below, give the type and value of the result of the listed expression. Each expression should be evaluated as if it were run at a specific point in the code, identified by one of the ‘Line’ comments. The first line has been filled in as an example.
- All of your answers in column 3 should be numbers. Write
addresses in hexadecimal with a leading
0xto indicate their base, and write integer and character values in decimal. - Assume that you are using a machine with 32-bit integers and 64-bit addresses using little-endian byte ordering.
- The
sizeoffunction returns the number of bytes taken up by one value of the specified type. If given a variable, it measures the size of that variable’s type. - The type for any
sizeofresult should besize_t, and the type for any result which is a difference between two addresses should beptrdiff_t. - Remember that when adding to or subtracting from a pointer to do address arithmetic, the integer added or subtracted will automatically be multiplied by the size (in bytes) of the kind of value that the pointer points to. Similarly, when subtracting two addresses their difference in bytes will be divided by their stride, to give an answer in “array units” rather than bytes.
- Remember that
x[y]is equivalent to*(x + y).
Exercise 1:
Evaluate these expressions as if they were run where it says “Line 1:”. The first few have been filled in already.
| Expression | Type | Value |
|---|---|---|
start |
int * |
0xa0000 |
end |
int * |
0xa0010 |
startStr |
char * |
0xa0000 |
endStr |
char * |
0xa0010 |
start + 1 |
Correct answer: int * | Correct answer: 0xa0004 |
&start[1] |
Correct answer: int * | Correct answer: 0xa0004 |
&startStr[1] |
Correct answer: char * | Correct answer: 0xa0001 |
&start[-1] |
Correct answer: int * | Correct answer: 0x9fffc |
(start + 1) - start |
Correct answer: ptrdiff_t | Correct answer: 1 |
&start[1] - &start[0] |
Correct answer: ptrdiff_t | Correct answer: 1 |
&startStr[1] - &startStr[0] |
Correct answer: ptrdiff_t | Correct answer: 1 |
endStr - startStr |
Correct answer: ptrdiff_t | Correct answer: 16 |
end - start |
Correct answer: ptrdiff_t | Correct answer: 4 |
(char*) (start + 1) - startStr |
Correct answer: ptrdiff_t | Correct answer: 4 |
sizeof(start) |
Correct answer: size_t | Correct answer: 8 |
sizeof(*start) |
Correct answer: size_t | Correct answer: 4 |
sizeof(startStr) |
Correct answer: size_t | Correct answer: 8 |
sizeof(*startStr) |
Correct answer: size_t | Correct answer: 1 |
(start + 1)- (int*) (startStr + sizeof(int)) |
Correct answer: ptrdiff_t | Correct answer: 0 |
These expressions should be evaluated as if the code was at “Line 2,”
where the items in the startStr array have been set to new
values:
| Expression | Type | Value |
|---|---|---|
*startStr |
Correct answer: char | Correct answer: 2 |
*(startStr + 1) |
Correct answer: char | Correct answer: 1 |
*start |
Correct answer: int | Correct
answer: 258 Hint: Each char
is 2 hex digits. Order is little-endian. |
These expressions should be evaluated as if the code was at “Line 3,”
after *start has been updated.
| Expression | Type | Value |
|---|---|---|
startStr[0] |
Correct answer: char | Correct answer: 3 |
startStr[1] |
Correct answer: char | Correct answer: 2 |
startStr[2] |
Correct answer: char | Correct answer: 0 |
*start |
Correct answer: int | Correct answer: 515 |
*((char *)start) |
Correct answer: char | Correct answer: 3 |
*(((char *)start) + 1) |
Correct answer: char | Correct answer: 2 |
(*((char *)start)) + 1 |
Correct answer: char | Correct answer: 4 |
Extra Exercises: More Pointer Programming
Exercise 2:
In the main notebook you created an arrayOps.c file. In
that file, implement a function called unzip and another
called freeUnzipped. unzip should take an
array and its length, and should allocate and return a 2-item array of
int* pointers, each of which will point to a
newly-allocated array of integers. The first array of the pair will hold
all the even-index entries from the given array, while the second entry
of the pair will hold odd-index entries. Make sure to use
malloc with appropriate sizes.
freeUnzipped will be given an array-of-two-arrays in the
style that unzip produces and will free both sub-arrays and
then free the main pair array, so that everything gets cleaned up. If
one or both of the sub-arrays is NULL for some reason,
freeUnzipped should not attempt to free it.
Use test-driven development (there are a number of interesting edge cases you’ll need to decide how to handle).
You can compare your solution and tests against those in
arrayOpsSolution.c. Of course our tests may not be valid if
you made different design choices.
Exercise 3:
In arrayOps.c, use test-driven development to implement
longestRun It should take three parameters: an array of
integers, its length, and a pointer to a length-result variable to
update. It will find the longest run of integers in the array and return
an array containing those integers, setting the length-return variable
to the length of the run.
You can compare to the solution in arrayOpsSolution.c,
although again, there are lots of design decisions to make (e.g., what
counts as a run? Are we allocating a new array or returning a pointer to
somewhere inside the array we’re given? What happens with the
length-result variable when the input is invalid? etc.).
Extra Exercises: More Predictions
For practice reading code that uses pointers and a look at some
surprising things that can happen, look at the files
main9.c, mystery0.c, and
mystery1.c: open each file, read the C program, predict the
printed output, then compile and run the program to verify your
predictions. Explain any surprising behavior in the actual output.
Exercise 4:
Predict the output of main9.bin:
Prints the following:
- The letter ‘P’ (hex 0x50 is ASCII capital P; that’s the byte stored at the start of the 4 bytes that together form the 4th integer in the array ‘a’ (at index 3).
- The letter ‘A’ (hex 0x41, stored in the MSB of a[0], which we get because we cast ‘a’ to a char* before adding 3).
- The letters “D C B A H G F E L K J I P O N M” (we’re iterating through the whole array of ints printing each byte as a char, but they’re in little-endian order.
Was there anything surprising about the output of
main9?
Example answer: The fact that the numbers are little-endian and so you don’t get the alphabet in order is probably surprising…
Predict the output of mystery0.bin:
Prints:
a = "hello!"
b = "hello!"
a = "cs 240"
b = "hello!"
a = "cs0xF"
b = "hello!"Was there anything surprising about the output of
mystery0.bin?
Example answer: The behavior of the final block is a bit tricky. Since the mystery function (which copies strings) writes a NUL byte at the end, the “cs 240” string is effectively shortened.
Predict the output for mystery1.bin:
Note: this one is basically impossible to predict correctly, even ignoring that you can’t predict stack addresses!
Prints:
x = 19
p = <some stack address starting with 0x7ff…>
a = "Hello!"
b = "Hi!"Now the message “Hi, CS 240!” gets written starting from b, and since
b only has space for 4 bytes, the “Hi,” goes into b, and further bytes
overwrite other values. It would be reasonable to assume that the 4
bytes used to store the integer x are next after the array b, followed
by the 8 bytes for a and then the 8 bytes for p. However, if we run the
program with gdb we can use the print command to show the
addresses of our variables: print &p then
print &a, etc. It turns out that the ordering is b,
then a, then p, then x, and there’s a 4-byte gap between p and x. The
ordering probably has to do with their types, although I’m not certain
on the details of how the compiler makes these decisions. The gap for x
has to do with “alignment” which we’ll see more about later.
This means that the rest of the string, including the NUL terminator,
fits neatly into a.
So we print:
x = 19
p = <same stack address as before>
a = "CS 240!"
b = "Hi, CS 240!"Importantly, even though b is only 4 bytes, when we print it, the print code runs until it sees the NUL byte, regardless of how big b is “supposed” to be.
Next up, we have the string “What happens if we use a really really long string?” This will overwrite not only all of the variables on the stack, but also bookkeeping information like the return address, causing a segmentation fault when the function returns (but it’s not going to do that just yet…). These bytes are assigned to our variable as follows:
- “What” to b
- ” happens” to a
- ” if we u” to p
- ” rea” to x after placing “se a” into the gap between p and x
- The rest of the string overwrites critical stack information beyond the variables.
Hex values for ” if we u” are 0x20, 0x69, 0x66, 0x20, 0x77, 0x65, 0x20, and 0x75. They will form 0x7520657720666920 in little-endian order, so that’s what p will show as.
Meanwhile, the values for x (” rea”) are 0x20, 0x72, 0x65, and 0x61, which in little-endian order is hex 0x61657220. A calculator tells me that that value is 1634038304 in decimal, which is what will be printed for x.
So we print:
x = 1634038304
p = 0x7520657720666920
a = " happens if we use a really really long string?"
b = "What happens if we use a really really long string?"
Note that once again the a and b prints don’t care that information is written ‘outside’ of the variable; they just continue printing until NUL is found.
Finally, we try to copy ‘Hi?’ to p, but since the value of p got scrambled, we get a segmentation fault here.
Surprises in actual output for mystery1:
Everything is a surprise! The exact way memory gets corrupted on the second and third writes is surprising, and the segmentation fault or other weird behavior for the last block is definitely surprising.
Especially surprising to me was the way that the variables were laid
out in memory on the stack. I had to triple-check with gdb
to know what their addresses were in order to predict the results from
the really long string, which I got wrong on my first 2-3 attempts.
Extra Exercises: More Pointer Code
Exercise 5:
If you want extra practice, the practiceSolutions.c file
contains solutions for the functions in practice.c, which
already have documentation and tests set up.
You can compile and run the file with:
make practice.bin
./practice.binYou may wish to add | less to the
second command to paginate the output. If you do, use ‘q’ to quit
pagination when you’re done.
