Consider the following program:
struct ptrs {
int** x;
int* y;
};
struct ptrs global;
void setup(struct ptrs* p) {
int* a = malloc(sizeof(int));
int* b = malloc(sizeof(int));
int* c = malloc(sizeof(int));
int* d = malloc(sizeof(int));
int* e = malloc(sizeof(int) * 2);
int** f = malloc(4 * sizeof(int*));
int** g = malloc(sizeof(int*));
*a = 0;
*b = 0;
*c = (int) a;
*d = *b;
e[0] = 29;
e[1] = (int) &d[100000];
f[0] = b;
f[1] = c;
f[2] = 0;
f[3] = 0;
*g = c;
global.x = f;
global.y = e;
p->x = g;
p->y = &e[1];
}
int main(int argc, char** argv) {
stack_bottom = (char*) &argc;
struct ptrs p;
setup(&p);
// *** examine memory here ***
}
This program allocates objects a through g on the heap and then stores
those pointers in some stack and global variables. We recommend you draw a
picture of the state setup creates.
Assume that (uintptr_t) a == 0x8300000, and that calls to malloc returns
increasing addresses. Match each address to the most likely expression with
that address value. The expressions are evaluated in main at the highlighted
point. You will not reuse an expression.
| Value | Expression | |||
|---|---|---|---|---|
| 1. | 0x8300040 | A. | &p |
|
| 2. | 0x8049894 | B. | (int*) *p.x[0] |
|
| 3. | 0x8361AF0 | C. | &global.y |
|
| 4. | 0x8300000 | D. | global.y |
|
| 5. | 0xBFAE0CD8 | E. | (int*) *p.y |