CSSE 132 Homework Review
Homework 3 Review: Memory Choices and Array Scans
Review the written portion and the C and ARM64 coding portion together. The same habit helps in all three: track what each value means, check the stopping condition, and explain why your result makes sense. Allow about 10 minutes.
Why This Matters
Homework 3 asks you to justify memory-system tradeoffs, calculate average access time, and scan arrays and strings. These are more than isolated exercises: a program needs both the right storage and correct accesses to that storage. Use this review alongside the assignment and the comments in your starter files.
Written Portion: Choose Memory for Its Job
Core idea: speed alone does not determine the best memory. Capacity, cost, physical size, and whether data survives power loss also matter. A small, fast layer can serve frequent requests while a larger, slower layer stores more data.
| Technology | Typical role | Tradeoff to explain |
|---|---|---|
| SRAM | Fast CPU storage, including caches | Fast and volatile; more expensive and less dense than DRAM. |
| DRAM | Main memory | Volatile; offers greater capacity at lower cost than SRAM, but slower access. |
| Flash / SSD | Persistent storage | Nonvolatile; slower than main memory, suitable for retaining files. |
| Hard disk | Persistent storage | Nonvolatile; mechanical access is slow, with large storage capacity. |
When explaining a design, connect the property to the requirement. For example, a small fast cache and a large backing store meet different needs. A machine that must retain files while powered off needs persistent storage. “Faster” is useful evidence, but it is not a complete justification.
Worked Example: Average Access Time
Use the homework model: every request first checks the cache; a miss adds a backing-store access. Thus the backing-store time is an additional miss penalty.
Average access time = cache access time + miss rate × miss penalty. Convert the percentage to a fraction and put both times in the same unit before calculating.
Suppose a cache takes 80 ns, the additional disk access takes 12 ms, and the miss rate is 0.02%.
Miss rate = 0.02 / 100 = 0.0002
12 ms = 12 × 1,000,000 ns = 12,000,000 ns
Average = 80 ns + 0.0002 × 12,000,000 ns
= 80 ns + 2,400 ns
= 2,480 ns
This exceeds 80 ns even though misses are rare: a disk access is enormously slower. If the miss rate becomes zero, the formula gives exactly the cache time. That is a useful sanity check.
ARM64 Portion: Keep State While Scanning
The starter asks for sumArray and max. Both receive the array address in x0 and the element count in w1; both return an int in w0. Array elements are 4-byte integers. Addresses and byte offsets use x registers; integer values use w registers.
A Small Trace
For {-6, 9, -2}, distinguish an element index from its byte offset:
| Index | Byte offset | Value | Total after this item | Largest seen |
|---|---|---|---|---|
| 0 | 0 | -6 | -6 | -6 |
| 1 | 4 | 9 | 3 | 9 |
| 2 | 8 | -2 | 1 | 9 |
A sum starts from zero. A maximum starts from an actual array element; zero is not a valid initial candidate for an all-negative array. The max contract guarantees a positive length.
Before each load, establish that an element remains. After processing it, advance the index by 1 and the byte offset by 4. Use signed comparisons for signed array values. Give each function distinct loop labels.
ldr w4, [x0, x3] // read one int at base + byte offset
add x3, x3, 4 // advance offset by one int
These are building blocks, not a complete function. Keep the base address intact until all loads finish: writing w0 also changes x0. Only put the final result in w0 when you no longer need that address.
C Portion: Match the Function Contract
Each C function has a different result: printed output, a count, an in-place change, or a pointer. Read its comments before deciding what to return.
print_odd: Count First, Then Format
For {-5, 4, 7, 0}, two elements are odd. In C, test whether the remainder after division by 2 is nonzero; a negative odd integer can have remainder -1. Count across the specified length, then choose the required message. The function returns void.
The starter specifies exact strings: There are N odd numbers in the array.\n or No odd number is found.\n. Replace N with the count, and preserve capitalization, spaces, punctuation, and newline. The test harness captures printf output for comparison.
length and lengthNoArrays: Stop at the Terminator
A string such as "Map" occupies four characters in memory: 'M', 'a', 'p', '\0'. Its length is 3 because the terminator is not part of the text. The empty string starts with '\0' and has length zero.
string[i] and *(string + i) access the same character. The exercise intentionally separates these styles: length must use brackets; lengthNoArrays must not. Neither may call another function. Advancing a char * by 1 moves one character, unlike an ARM64 byte offset for an int array.
upcaseVowels: Mutation and Count Are Separate Results
For writable text char text[] = "blue Sky";, the expected changed text is "blUE Sky" and the count is 2. Visit characters until '\0', change only lowercase a, e, i, o, u, and count only characters actually changed. Existing uppercase vowels and consonants stay as they are.
The function modifies the original buffer rather than making a copy. Use a writable character array when testing; do not attempt to modify a string literal.
searchStructID: Return an Address in the Original Array
The array contains struct student values, each with name[12] and id. For a generic index i, array[i].id accesses the ID and array[i].name yields a pointer to the first character of that record's name. With a pointer to one record, use record->id.
Compare IDs, then return the matching name's address. The result is a char *, not the numeric ID or a new local buffer. The contract guarantees a positive length and an existing match; keep accesses within the supplied length. Pointer arithmetic on a struct pointer advances by a whole struct, not one byte.
Common Mistakes
0.02% means 0.0002, not 0.02. Convert milliseconds to nanoseconds before adding.
A length of 3 permits indices 0, 1, and 2. Check the bound before loading.
'\0'The terminator stops the scan; it does not increase the length.
For vowel conversion, verify both the modified string and the returned count. For struct search, verify the returned address points into the original record.
Quick Reference and Testing
| Remember | Rule |
|---|---|
| Time units | 1 ms = 1,000,000 ns; percentage ÷ 100 gives the rate. |
| ARM64 int element | Byte offset = index × 4; result in w0. |
| C string | Stop before '\0'; pointer increments scale by pointed-to type. |
| Struct access | Value: .; pointer: ->. |
Assembly on the course Linux VM
make test_asm
./test_asmC on the course Linux VM
make test
./testRun these commands inside your Homework 3 directory. The two executables test different source files; passing one does not verify the other. Keep the supplied function names and signatures, and do not add main to problems.c. The homework directs you to submit your modified problems.s and problems.c to Gradescope.
Check Yourself
- A 60 ns cache has an additional 5 ms miss penalty and a 0.004% miss rate. What is the average access time?
- Why does a machine that must retain files through power loss need more than ordinary DRAM?
- What byte offset selects
array[4]in an ARM64intarray? Why can starting a maximum at zero fail? - For writable text
"Echo", what are its length, its text after vowel conversion, and the number changed? - For a matching student record, should a name lookup return its ID, a local copy, or a pointer to its name field?
Answers and Reasoning
0.004 / 100 = 0.00004;5 ms = 5,000,000 ns. Average:60 + 0.00004 × 5,000,000 = 260 ns.- DRAM is volatile: its contents are lost without power. Persistent files need nonvolatile storage.
4 × 4 = 16bytes. Zero would incorrectly beat every element in an all-negative array.- Length 4; changed text
"EchO"; count 1. The E was already uppercase and the terminator is not counted. - Return a pointer to the matching record's name field in the original array. It has the required
char *type and remains part of the caller's storage.