Vous êtes sur la page 1sur 7

CS107 Handout #8

J Zelenski Nov 30, 2010


Final practice

Final Exam: Thursday, Dec 9th 8:30-11:30am


Bishop Auditorium (in the GSB)

This is our official registrar-scheduled exam time. There is no alternate exam. No


electronic devices are allowed, but you may bring your textbook, notes, and other paper
resources.

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:

• C— strings, arrays, pointers, &, *, void*, typecasts, function pointers


• Data representation—bits, bytes, ASCII, two's complement integers, floating
point, layout of arrays and structs, pointers
• IA32 assembly—data access and addressing modes, arithmetic and logical ops,
implementation of C control structures, function call/return, register use
• Address space—layout and purpose of text/data/stack/heap segments, handling
of globals /locals/parameters
• Runtime stack— protocol for function call/return, parameter passing,
management of ebp and esp registers
• Compilation— tasks handled by preprocessor, compiler, assembler, and linker,
static and dynamic linking, relocatable object files and executables, makefiles
• Memory— memory hierarchy, caches, locality, static versus dynamic
allocation, heap allocator strategies and tradeoffs
• Performance— compiler optimizations, measuring execution time, profiling
• Python— simple Python programs, Python as antithesis of C: interpreted
versus compiled, dynamic versus static type system, safety versus efficiency,
etc. (no in-depth python questions)

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.

Good luck preparing!


–2–

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;

for ( local = 4 ; _____________________ ; local *= 4 ) {

__________________________________________;

__________________________________________;

__________________________________________;

__________________________________________;

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–

Problem 2: Runtime stack


Implement the GetArgv0 function to retrieve argv[0], i.e., the string in the first slot of
the array passed to main(int argc, const char *argv[]). The function operates by
crawling the runtime stack to find the stack frame for main and accesses its parameters.
The one argument to GetArgv0 is the size of the code for the main function. Your
function can assume the stack is not corrupted, it does contain a frame for main, and that
the argv array has at least one entry. Do not make assumptions about where main is
within the stack, e.g. expecting a certain count of frames before/after it. Hint: the code
size for main is passed as a parameter and there is a simple and direct way to get the base
address for main's code. It may be helpful to draw a picture of the stack layout to get your
bearings before you start.
char *GetArgv0 (int mainSize) // mainSize expressed in number of bytes

Problem 3: Heap allocator


The fact that immovable in-use blocks punctuate the free space is an impediment to
efficient memory utilization by the heap allocator. Consider the heap segment below. The
shaded in-use blocks show clients have pointers 0x100c, 0x1020, and 0x1030.
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

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

Master pointers 0x0 0x1020 0x100c 0x0 0x0 0x1030

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–

0x5400 5404 5408 540c 5410 5414

Master pointers 0x0 0x100c 0x1004 0x0 0x0 0x101c

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;

static header *heapStart; // addr of first header in heap segment


static header *heapEnd; // addr past end (0x1038 in prev diagram)
static void **masters; // array of master pointers
static int nMasters; // number of slots in above array

#define INUSE_MASK (1 << 31) // masks for inuse/size bits in header


#define SIZE_MASK (~(1 << 31))

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

a: Re-implement NumWords to compute an equivalent result with only 3 assembly


instructions in the function body. Show both the C code and the generated assembly. You
can hard-code knowledge that word size is the constant 4.

NumWords:
size_t NumWords(size_t nBytes) push %ebp # prolog
{ mov %esp,%ebp
mov 0x8(%ebp),%eax # first body inst
return

pop %ebp # epilog


ret

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–

Problem 4: Short answer


a: As seen in lab, comparison of floating point values using == is unreliable. One possible
workaround is to test for the absolute value of the difference being "small enough", e.g.,
bool ApproximatelyEqual(float f1, float f2)
{
return fabs(f1 – f2) <= FLT_EPSILON;
}

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.

b: Consider the complete source file below.


/* File: program.c
* ---------------
* Take note: no #includes
*/

int main(int argc, const char *argv[])


{
// code is nonsense, but harmless
assert(argc != 0);
fopen(argv[0], "r");
CVectorAlloc(sizeof(int), 10, NULL);
return 0;
}

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.

Describe the necessary changes to make this program build cleanly.

Vous aimerez peut-être aussi