| Address | Value |
|---|---|
0x100 |
0x78 |
0x101 |
0x56 |
0x102 |
0x34 |
0x103 |
0x12 |
0x100 if the Machine is0x12345678
0x78563412
y and z in Hexadecimal and Explain why They Differint x = 0xFFFFFF80;
int y = x >> 4;
int z = x >>> 4;
y = 0xFFFFFFF8; z = 0x0FFFFFF8;
They differ because >> pads based on the sign if the type is signed where as >>> always pads with zeroes.
*(p+1) on a Little-endian Machine? Show the Byte Layout of a in Memoryint a = 0x12345678;
char *p = (char*)&a;
Memory Layout
| Address | Value |
|---|---|
a |
0x78 |
a+1 |
0x56 |
a+2 |
0x34 |
a+3 |
0x12 |
*(p+1) = 0x56
struct S {
int a;
int b;
int c;
};
struct S arr[5];
Assume sizeof(int) = 4 and arr begins at address 0x200.
arr[3].b. Show how the Array Index and Field Offset Contribute to the Final Addresssizeof(S) = 12;
&arr[3] = 0x224;
&arr[3].b = 0x224 + 0x4;
= 0x228;
r1 Contains the Base Address of arr. What Constant Must Be Added to r1 to Access arr[2].c?= 24 + 8 = 32
All the address calculations are known statically at compile time.
int A[5] = {2, 4, 6, 8, 10};
int *p = &A[1];
int *q = &A[3];
int **pp = &p;
*p = *q - A[0];
*(*pp + 2) = *p + *q;
pp = &q;
*(*pp - 1) = *(*pp - 1) + **pp;
p = &A[0];
**(&pp) = p;
*(*pp + 4) = *p + *(*pp + 1);
A[0] through A[4], the Values of p, q, and pp, and Identify Which Memory Location Was Modified| Variable | Value |
|---|---|
A |
{2, 4, 6, 8, 10} |
p |
&A[1] |
q |
&A[3] |
pp |
&p |
*p = *q - A[0];
| Variable | Value |
|---|---|
A |
{2, 6, 6, 8, 10} |
p |
&A[1] |
q |
&A[3] |
pp |
&p |
*(*pp + 2) = *p + *q;
| Variable | Value |
|---|---|
A |
{2, 6, 6, 14, 10} |
p |
&A[1] |
q |
&A[3] |
pp |
&p |
pp = &q;
| Variable | Value |
|---|---|
A |
{2, 6, 6, 14, 10} |
p |
&A[1] |
q |
&A[3] |
pp |
&q |
*(*pp - 1) = *(*pp - 1) + **pp;
| Variable | Value |
|---|---|
A |
{2, 6, 20, 14, 10} |
p |
&A[1] |
q |
&A[3] |
pp |
&q |
p = &A[0];
| Variable | Value |
|---|---|
A |
{2, 6, 20, 14, 10} |
p |
&A[0] |
q |
&A[3] |
pp |
&q |
**(&pp) = p;
| Variable | Value |
|---|---|
A |
{2, 6, 20, 14, 10} |
p |
&A[0] |
q |
&A[0] |
pp |
&q |
*(*pp + 4) = *p + *(*pp + 1);
| Variable | Value |
|---|---|
A |
{2, 6, 20, 14, 8} |
p |
&A[0] |
q |
&A[0] |
pp |
&q |
AA = {2, 6, 20, 14, 8}
struct Inner {
int x;
int y;
};
struct Outer {
int a;
struct Inner b;
struct Inner *p;
};
struct Outer s;
struct Outer *d;
int *ip;
&s.b.y Be Computed Entirely Statically, or Must Some part Be Computed Dynamically? Briefly JustifyYes it can s is allocated statically and the same with the offsets to b.y.
&d->b.y Be Computed Entirely Statically, or Must Some part Be Computed Dynamically? Briefly JustifyNo it can't as d is allocated dynamically so the offsets can be static but the base is dynamic.
&s.p->x Be Computed Entirely Statically, or Must Some part Be Computed Dynamically? Briefly JustifyNo it can't the offset to s.p can be but p is dynamic, the offset to x can be static though but the base p must be dynamically determined.
&d->p->y Be Computed Entirely Statically, or Must Some part Be Computed Dynamically? Briefly JustifyIt must be computed dynamically but some offsets can be computed statically like the offset from the base of d to p but the base address d is dynamic
struct A {
int a[3];
};
struct B {
struct A x0;
struct A *x1;
};
struct C {
struct B b;
struct B *c[6];
};
struct C s;
struct C *d;
*(s.b.x1->a + 2)? Exclude Instruction Fetches2
d->b.x1->a[1]? Exclude Instruction Fetches3
d->c[5]->b.x0.a[2]? Exclude Instruction Fetches3
struct Node {
int val;
struct Node* next;
};
struct Node* f() {
struct Node* a = malloc(sizeof(struct Node));
a->val = 1;
struct Node b;
b.val = 2;
a->next = &b;
free(a);
return a;
}
Setting a->next=&b and free(a) before it is returned.
b is allocated on the stack of f so when f returns it gets deallocated setting a->next to be b will cause errors as it will point to other memory after f returns. Additionally a is freed before it returns which again is a use after free issue.
struct Node {
int val;
struct Node* next;
};
struct Node* f() {
struct Node* a = malloc(sizeof(struct Node));
a->val = 1;
struct Node* b = malloc(sizeof(struct Node));
b->val = 2;
a->next = b;
return a;
}
int g(int *a, int n) {
int t = a[n - 1];
return t + 1;
}
int f(int *a, int n) {
int x = n + 2;
int y = g(a, x);
return y + a[0];
}
Assume the call executed is f(A, 3).
g is Called but before g Begins Executing Its Body| Description | Value |
|---|---|
*a |
A |
n |
3 |
| Return Address | |
x |
5 |
y |
|
*a |
A |
n |
5 |
| Return Address |
g after Local Variable t Has Been Allocated and Initialized| Description | Value |
|---|---|
*a |
A |
n |
3 |
| Return Address | |
x |
5 |
y |
|
*a |
A |
n |
5 |
| Return Address | |
t |
A[4] |
f and g, Identify Parameters, Local Variables, and return AddressesfParameters:
*anLocal Variables
xyReturn Addresses
f's callergParameters:
*anLocal Variables
tReturn Addresses
g's callerg is DynamicIt's dynamic because g could be called from multiple different locations in the program this call location must be determined at runtime.
A Begins at Address 0x1000. Show the Values Passed to g when f(A, 3) Executesg(0x1000, 5)
The offsets and sizes of the records can be determined statically but their actual locations in memory and values must be determined dynamically. The return address must also be determined dynamically.
int sum(int *A) {
int s = 0;
for (int i = 0; i < 4; i++)
s += A[i];
return s;
}
Write SM213 Assembly Implementing the following Procedure. Assume the Argument is Passed in r0 and the return Value is Placed in r0. Include a Prologue, Epilogue, and Any Local Variable Storage You Need
int f(int x) {
int y = x + 3;
return y * 2;
}
f:
deca r5
st r6, 0(r5)
deca r5
ld $3, r2
add r0, r2
st r2, 0(r5)
shl $1, r2
mov r2, r0
inca r5
ld (r5), r6
inca r5
j (r6)
caller:
deca r5
st r6, 0(r5)
deca r5
st r1, 0(r5)
ld $arr, r0
ld $4, r1
gpc $6, r6
j foo
ld 0(r5), r1
inca r5
ld 0(r5), r6
inca r5
j (r6)
foo:
deca r5
st r6, 0(r5)
deca r5
st r2, 0(r5)
deca r5
st r3, 0(r5)
ld $0, r2
ld $0, r3
L1:
beq r1, L4
ld (r0), r4
bgt r4, L2
L3:
add r4, r2
inca r0
dec r1
br L1
L2:
not r4
inc r4
add r4, r3
br L3
L4:
add r3, r2
mov r2, r0
ld 0(r5), r3
inca r5
ld 0(r5), r2
inca r5
ld 0(r5), r6
inca r5
j (r6)
Caller Prologue:
deca r5
st r6, 0(r5)
deca r5
st r1, 0(r5)
ld $arr, r0
ld $4, r1
gpc $6, r6
Caller Epilogue:
ld 0(r5), r1
inca r5
Callee Prologue:
deca r5
st r6, 0(r5)
deca r5
st r2, 0(r5)
deca r5
st r3, 0(r5)
Callee Epilogue:
mov r2, r0
ld 0(r5), r3
inca r5
ld 0(r5), r2
inca r5
ld 0(r5), r6
inca r5
j (r6)
Loop:
L1:
beq r1, L4
ld (r0), r4
bgt r4, L2
L3:
add r4, r2
inca r0
dec r1
br L1
L2:
not r4
inc r4
add r4, r3
br L3
If / Else Structure:
beq r1, L4
ld (r0), r4
bgt r4, L2
L3:
add r4, r2
inca r0
dec r1
br L1
L2:
not r4
inc r4
add r4, r3
br L3
Function Call:
gpc $6, r6
j foo
Return Instruction:
mov r2, r0
# ...
ld 0(r5), r6
inca r5
j (r6)
fooint foo(int* arr, int len) {
int a = 0;
int b = 0;
while (len != 0) {
if (arr[0] > 0) {
b -= arr[0];
a -= arr[0];
} else {
a += arr[0];
}
arr += 1;
len -= 1;
}
return a + b;
}
void map(int *dst, int *src, int n, int (*f)(int));
int foldr(int *A, int n, int (*f)(int, int), int base);
map applies f to each element of src and stores the results in dst. foldr is defined by:
foldr(A, n, f, base) = base if n == 0 = f(A[0], foldr(A+1, n-1, f, base)) otherwise
mapvoid map(int *dst, int *src, int n, int (*f)(int)) {
for (int i = 0; i < n; i++) {
dst[i] = f(src[i]);
}
}
foldrint foldr(int *A, int n, int (*f)(int, int), int base) {
if (n == 0) return base;
return f(A[0], foldr(A+1, n-1, f, base));
}
int add(int x, int y);int add(int x, int y) {
return x + y;
}
foldr({1, 2, 3, 4}, 4, add, 0)foldr({1, 2, 3, 4}, 4, add, 0); // 1 + 2 + 3 + 4
// 10
void map(void **dst, void **src, int n, void (*f)(void*));
void* foldr(void **A, int n, void* (*f)(void*, void*), void* base);
void map(void **dst, void **src, int n, void (*f)(void*)) {
for (int i = 0; i < n; i++) {
dst[i] = f(src[i]);
}
}
void* foldr(void **A, int n, void* (*f)(void*, void*), void* base) {
if (n == 0) return base;
return f(A[0], foldr(A+1, n-1, f, base));
}
int total = 0;
void second(int v) {
total = total + v;
}
void first(int v) {
total = total + v;
diskRead(v + 1, second);
}
int main() {
diskRead(3, first);
total = total + 100;
}
diskRead Calls, the Callbacks, and the Modification of total in maindiskRead(3, first);
total = total + 100;
total = total + v;
diskRead(v + 1, second);
total = total + v;
total = total + 100 May Execute before first RunsBecause if the disk read does not finish before the instruction pointer moves total = total + 100 can execute first.
10 and the Second Returns 7. What is the Final Value of total?total = 100 + 10 + 7
= 117
Asynchronous control flow can lead different parts of code to non deterministically run at different times allowing for flow to move in different ways depending on the run where as ordinary sequently procedure call flow will always run from top to bottom sometimes jumping up in the exact same order each time.
Define and Explain the Relationships between the following Terms: Interrupt, Polling, DMA, Thread, and Critical Section. Your Answer Must Describe at Least One Interaction between Two or More of These Concepts in a Real System
int x = 0;
pthread_mutex_t m;
Thread 1:
lock(m);
x = x + 1;
printf("%d\n", x);
unlock(m);
Thread 2:
lock(m);
x = x + 2;
printf("%d\n", x);
unlock(m);
1 3
2 3
Because the locking allows only two possible results either thread 1 acquires the lock first causing thread 2 to pause until it completes or thread 2 acquires the lock first causing the inverse.
If the mutexes were removed there the additions could run together before the prints or the other two outputs before could happen. Additionally write instructions might happen after read instructions leading the values to be clobbered by each other for example we could end up with just 1 or just 2 as the final total.
int ready = 0;
Thread 1:
produce_data();
ready = 1;
Thread 2:
while (ready == 0);
consume_data();
Because it requires constant polling from thread 2 which uses cpu cycles.
int producer_acquired = 0;
pthread_mutex_t m = uthread_mutex_create();
uthread_cond_t ready = uthread_cond_create(m);
Thread 1
uthread_mutex_lock(mx);
produce_data();
producer_acquired = 1;
uthread_cond_broadcast(ready);
uthread_mutex_unlock(mx);
Thread 2
uthread_mutex_lock(mx);
while (producer_acquired == 0) {
uthread_cond_wait(ready);
}
consume_data();
uthread_mutex_unlock(mx);
They allow the data consumption to wait until it's been produced without wasting polling on checking a variable it also stops deadlocks.