Académique Documents
Professionnel Documents
Culture Documents
Material
The final is comprehensive but expect more coverage on post-midterm topics and
particular focus on material covered in the labs and assignments. Check your rear-view
mirror for the impressive list of things you've learned in 107:
The rest of this handout is the final from CS107 last spring so questions are fairly
representative in terms of format, difficulty, and content (note python was not covered).
To conserve paper, I removed answer space, but the real exam will have much more
room for your answers and scratch work. We'll distribute solutions on the course web site
later this week.
Problem 1: IA32
Below are two versions of the assembly code generated for the Winky function.
The left-side version was compiled –O0, the right-side –O2.
Winky: Winky:
push %ebp push %ebp
mov %esp,%ebp mov %esp,%ebp
sub $0x10,%esp mov $0x4,%edx
movl $0x4,-0x4(%ebp) mov 0xc(%ebp),%eax
jmp .L2 .L2:
.L3: cmpl $0x4,(%eax)
addl $0x10,0x8(%ebp) je .L1
mov 0xc(%ebp),%eax lea 0x4(,%edx,4),%edx
mov (%eax),%eax sub $0x8,%eax
cmp $0x4,%eax cmp $0x1,%edx
je .L1 jg .L2
mov 0xc(%ebp),%eax L1:
sub $0x8,%eax lea 0xc(%ebp),%eax
mov %eax,0xc(%ebp) pop %ebp
addl $0x1,-0x4(%ebp) ret
shll $0x2,-0x4(%ebp)
.L2:
cmpl $0x1,-0x4(%ebp)
jg .L3
.L1:
lea 0xc(%ebp),%eax
leave
ret
a: Fill in the blanks in the C code below for Winky to match the unoptimized assembly
from above left. Your code should refer to variables, not register names. Note this is
nonsense code, not intended to do something meaningful.
void *Winky(int param1, int *param2)
{
int local;
__________________________________________;
__________________________________________;
__________________________________________;
__________________________________________;
return ___________________;
b: Compare the unoptimized and optimized assembly for Winky. Identify three distinct
kinds of transformations made by the optimizing compiler and indicate why each change
is beneficial in this context.
–3–
A request is made for a 12-byte block. Although the heap segment has 12 bytes available,
they are not contiguous and the in-use blocks cannot be moved to coalesce the free space.
Servicing this request would require extending the heap segment.
The original Macintosh operating system worked around this by supplying memory to the
client in the form of handles. Instead of returning a void* pointer to a heap block, the
allocator returns a void** handle which is a pointer to a pointer to a heap block. The
pointer to the heap block is called a master pointer and the allocator maintains the master
pointers. With the addition of handles, the heap from above becomes:
0x5400 5404 5408 540c 5410 5414
Heap segment
nWrds 2 nWrds 2 nWrds 3 nWrds 4 nWrds 3
inUse 0 inUse 1 inUse 0 inUse 1 inUse 1
0x1000 1004 1008 100c 1010 1014 1018 101c 1020 1024 1028 102c 1030 1034
Clients have handles 0x5404, 0x5408, and 0x5414. The client who requested a payload of
8 bytes was given the handle 0x5414. The client dereferences the handle to get address
0x1030 where they store their data.
Because the client has handles, the allocator can rearrange heap blocks as long as the
master pointers are updated. Below shows the same heap after compacting. All in-use
blocks are grouped at the base of the heap, the free space is coalesced at the end, and the
master pointers have been correctly updated.
–4–
Heap segment
nWrds 2 nWrds 4 nWrds 3 nWrds 5
inUse 1 inUse 1 inUse 1 inUse 0
0x1000 1004 1008 100c 1010 1014 1018 101c 1020 1024 1028 102c 1030 1034
After compacting, satisfying that 12-byte request is no problem! Note that clients don't
need to know when or how the allocator has rearranged the heap segment. The client who
has handle 0x5414 dereferences it to obtain address 0x101c, which is the heap block that
now contains their data.
You are implementing a simple handle allocator with the following specifications.
The heap segment uses a block header, no footer, and maintains an implicit free list. The
design is built assuming a 4-byte word. The header is one word, a 4-byte unsigned int.
The most significant bit of the header records whether the block is in use and the
remaining bits store the total block size (payload + header) expressed as a count of 4-byte
words. All requested payload sizes are rounded up to a multiple of 4-byte words and all
heap blocks are aligned to 4-byte boundaries.
The master pointers are stored in an array. Each unused/freed slot contains NULL.
Below are the global variables and type definitions for the allocator.
typedef unsigned int header;
The FindMaster helper function searches the master array and returns the index of the
first unused entry or –1 if all are in use. You are given the code for this function and it
works correctly.
static int FindMaster()
{
for (int i = 0; i < nMasters; i++)
if (masters[i] == NULL) return i;
return -1;
}
–5–
The NumWords helper function is given a size in bytes and returns the minimum number
of 4-byte words required to store that number of bytes. The code below on the left works
correctly, but the direct translation into unoptimized assembly below on the right uses an
expensive divl operation and requires 8 total instructions for the function body, not
counting the function prolog/epilog.
NumWords:
size_t NumWords(size_t nBytes) push %ebp # prolog
{ mov %esp,%ebp
return nBytes/4 + mov 0x8(%ebp),%eax # body
((nBytes % 4) != 0); mov $0x0,%edx # clear for divl
} mov $0x4,%ecx # load divisor
divl %ecx # div edx:eax by ecx
# quotient in eax, remainder in edx
test %edx,%edx
setne %dl
movzbl %dl,%edx
addl %edx,%eax
pop %ebp # epilog
ret
NumWords:
size_t NumWords(size_t nBytes) push %ebp # prolog
{ mov %esp,%ebp
mov 0x8(%ebp),%eax # first body inst
return
The FindBlock helper function uses a first-fit algorithm to search the heap for an
available block of sufficient size and returns a pointer to its header or NULL if none was
found. This function compiles but has a critical logical error.
// nWords is total size of block needed (includes header in count)
static header *FindBlock(size_t nWords)
{
for (header *hdr = heapStart; hdr < heapEnd; hdr++)
if (!(*hdr & INUSE_MASK) && ((*hdr & SIZE_MASK) >= nWords))
return hdr;
return NULL;
}
b: Identify the error within FindBlock . Describe the symptom you would see when
testing this function. Show the changes necessary to correct the code.
–6–
c: Your partner suggests the test within the loop of FindBlock can be streamlined to just
one comparison like this:
if (*hdr >= nWords)
Your partner is on to something! This expression will work correctly given in the
inclusion of one or more essential typecasts. Indicate what typecast(s) must be inserted.
d: Write the NewHandle function. The function takes one argument, the requested
payload size in bytes, and returns a handle, a void** that points to a master pointer to a
correctly allocated heap block. The requested payload size is rounded up to a multiple of
4–byte words. It uses a first-fit search and does not split the block if larger than
requested. The function should properly update all allocator data structures. NewHandle
returns NULL if all masters are in use or the heap segment does not have a single existing
block of sufficient size. (Do not coalesce/compact; that comes later.) You should use the
helper functions NumWords, FindMaster, and FindBlock.
void **NewHandle(size_t nPayloadBytes)
e: Profiling your NewHandle function shows it runs too slowly. Both FindMaster and
FindBlock have linear loops and could be streamlined by maintaining an explicit free list
of masters or blocks, respectively. You only have time to work on one. Which one will
provide the more fruitful optimization? Explain your reasoning.
f: Write the CompactHeap function. This function moves the in-use blocks to the base of
the heap segment without gaps, creating one coalesced free block at the end. It may be
helpful to review the before/after diagrams on a previous page. To avoid overwriting
other blocks, process the blocks in order of low to high address (i.e. move the in-use
block at lowest address first, then move higher-address blocks). Access the in-use blocks
via the masters array, not by walking the heap segment. The master array is not kept in
sorted order, nor can you directly sort it, thus you must use a temporary array.
Here are the requirements for how the function must operate:
• Create a temporary array of pointers to in-use masters
• Use qsort to sort the temporary array in order of increasing block address
void qsort(void *baseOfArray, int numElements, int elemSize,
int (*cmp)(const void *, const void *));
• Iterate over the sorted array
- move each bock to the next available heap location
- update its master
• Coalesce any remaining heap space in the final block of the heap segment
You may define helper functions as needed. Be sure you understand the requirements and
pay close attention to details, as they will matter immensely in grading.
void CompactHeap()
–7–
To define "small enough" this code uses the constant FLT_EPSILON from <float.h>.
F L T _ E P S I L O N is defined as the minimum positive float value such that 1.0 +
FLT_EPSILON != 1.0 is true. Work out the value for FLT_EPSILON assuming a 32-bit
IEEE float as used by the myths. Explain how you arrived at your answer.
As written, ApproximatelyEqual is not a panacea. There are pairs of distinct float values
that are as close as possible (i.e., there is no representable float in between) for which
ApproximatelyEqual returns false. Give such a pair of float values with the minimum
magnitude.
Identify the warnings/errors when building this program using gcc –Wall program.c.
Assume the entire compilation chain runs, despite any earlier errors. For each problem,
indicate the tool (preprocessor, compiler, assembler, or linker) that reports it.