Warning! This site is under development.
This class will be recorded! Its use will be limited to members of the class. Please do not share with others.
Approximate overview
Last class, I mentioned function calls and the stack, and you all indicated that you understood them. I’ll give you a few minutes to talk in pairs and then I’ll ask a lucky “volunteer” (voluntold?) to explain.
MEMORY, MORE OR LESS C CODE
+------+ Stack v int sum(x,y) { return x+y; }
0xFFFF | 2 | z ...
+------+ int foo() {
0xFFFE | 5 | a int z; int a = 5;
+------+----- STACK FRAME z = sum(a,2); /* THIS CALL! */
0xFFFD | 7 | }
+------+
0xFFFC | 5 | x UNDERLYING "OPERATIONS"/"ACTIONS"
+------+ * Allocate space on the stack
0xFFFB | 2 | y * Copy the value from from the
+------+ address associated with a to
0xFFFA | | the address associated with x.
+------+ * Copy the constant 2 to the
0xFFF9 | | address associated with y.
+------+ * Do the computation, and put
0xFFF8 | | the result on the stack
+------+ * Copy the result back to z
0xFFF7 | | * Pop the stack
+------+
0xFFF6 | | ANYTHING MISSING?
+------+ * Ints really take four bytes
... * We also have to put other
+------+ info on the stack (e.g., old
0xF004 | | stack pointer)
+------+ * We have to "do" the function
0xF003 | | call. (Switch control
+------+ to another part of the code.)
0xF002 | | * At the end, return control.
+------+
0xF001 | |
+------+
0xF000 | |
+------+ Heap ^ / Static Stuff v
0xEFFF | |
+------+
...
How does it access the memory?
There is a register, called the stack pointer, and our memory references are in terms of the stack pointer. So
xis “one below the stack pointer” andyis “two below stack pointer” (depending on how you think of the stack pointer).
Moral: Even simple function calls can be expensive! We may want to avoid some of that.
Side note: Good optimizers avoid most of this work.
Don’t worry if you don’t fully understand this; they main idea is that function calls are “expensive”, requiring setup and takedown, which is why some people dislike recursion. It’s also why some people like macros.
You can learn more in CSC-211. Or on the Interweb. One is more reliable than the other.
As long as we’re thinking about memory layout. Suppose you can fit four bits in one cell. How would you lay out the letter ‘z’, which has decimal value 122 and binary value 0111 1010
little big
| ... |
+------+
0xF001 | 1010 | or 0111
+------+
0xF000 | 0111 | 1010
+------+ The Stack ^ / Static Stuff v
| ... |
When you think about memory horizontally, rather than vertically, which way do you number?
0xF002 0xF001 0xF000
-+------+------+------+-
... | | 1010 | 0111 | ...
-+------+------+------+-
or
0xF000 0xF001 0xF002
-+------+------+------+-
... | 0111 | 1010 | | ...
-+------+------+------+-
If you put the higher-order (leftmost, in our traditional ordering) bytes in higher addresses, you’re using big endian notation. If you put the the higher-order bytes in lower addresses, you’re using little-endian notation.
There are advantages to each, which you can learn about when you take CSC-211.
What happens when you try to store RGBA colors in an integer?
color = (r << 24) + (g << 16) + (b << 8) + a;
This confused my research students for a bit.
While I realize that “foolish consistency is a hobgoblin of little minds”, CS diagrams frustrate me.
Because macros copy text, we can get some weird behavior.
#define DOUBLE(X) 2*X
y = DOUBLE(2+3);
====
y = 2*2+3;
// Whoops! Y is 7, rather than 10
Solution: Parenthesize variables in your macros. (You may need to parenthesize other things, too.)
#define DOUBLE(X) 2*(X)
y = DOUBLE(2+3);
=====
y = 2*(2+3);
// Yay! It's 10
Note: You need a lot of parentheses
#define DOUBLE(X) (X)+(X)
y = 2*DOUBLE(5)
=====
y = 2*(5)+(5)
// Whoops 15, not 20
Solution: More parentheses!
#define DOUBLE(X) ((X)+(X))
int square(int x) { return x*x; }
#define SQUARE(X) ((X)*(X))
a = 1;
b = 1;
p = square(a++);
q = SQUARE(b++); // expands to q = ((b++)*(b++))
printf ("a=%d, b=%d, p=%d, q=%d\n", a, b, p, q);
// Expected output? a=2, b=3, p=1, q=6
// Expected output? a=2, b=3, p=1, q=1
a = 1;
b = 1;
p = square(++a);
q = SQUARE(++b);
// Expected output? a=3, b=?, p=4, q=?
Why did Sam show you this?
SQUARE(b++) only increments b once. That’s a problem
with the SQUARE macro, as I designed it.#define SILLY(X) ((X)-(X))
int
main (int argc, char *argv[])
{
int a, b, p, q;
a = 1;
p = SILLY(a++); // p = ((a++)-(a++))
printf ("a=%d, p=%d\n", a, p);
// Potential output? a=3, p=? -1,0,1
b = 1;
q = SILLY(++b); // q = ((++b)-(++b))
printf ("b=%d, q=%d\n", b, q);
// Potential output? b=3, q=?
} // main
Use backslashes to continue your line.
#define MACRO(X) \
(X+X) *
(X+X)
Sometimes, you want to execute multiple statements in one macro
#define STACK_POP2(head,result,next) \
(result) = (head); \
(head) = (head)->next;
What happens if we use it in a conditional or a loop?
if (x > 10)
STACK_POP2(stck,tmp,next);
Translates to
if (x > 10)
(tmp) = (stck); (stck) = (stck)->next;;
Two issues:
Solution: Wrap multiple statements in do { ... } while (0)
#define STACK_POP2(head,result,next) \
do { \
(result) = (head); \
(head) = (head)->next; \
} while (0)
utstack.hhttps://github.com/troydhanson/uthash/blob/master/src/utstack.h
What is puzzling/confusing about it?
next come from in, say STACK_PUSH?
STACK_PUSH and STACK_PUSH2?
What is nice about it?
Why doesn’t Sam like it? (Why do you hypothesize Sam doesn’t like it?)
STACK_PUSH(stack, item); and knowing
that stack is NULL. My expectation is that stack doesn’t
change. It should be NULL afterwards.Why don’t others like it
Sam’s first improvement
#define STACK_POP(HEAD,RESULT) \
STACK_POP2(HEAD,RESULT,next)
We’ll start this today and continue it next time.
Goal: Compute (x^n) mod m, where x, n, and m are all non-negative long
values.
Assume: x < m and m < sqrt(LONG_MAX).
Obvious solution:
long
expmod(long x, long n, long m)
{
long result = 1;
for (i = 0; i < n; i++)
{
result = (result * x) % m;
} // for
return result;
} // expmod
mod each time through so that we don’t overflow.Can we do a more efficient solution?
long
expmod(long x, long n, long m)
{
if (n == 0) // x^0 = 1
{
return 1;
}
else if ((n % 2) == 0) { // even
long tmp = expmod(x, n/2, m); // x^(2k) = x^k * x^k
return (tmp * tmp) % m;
}
else {
long tmp = expmod(x, n-1, m); // x^(k+1) = x^k * x
return (x * tmp) % m;
}
} // expmod
This is much faster. But it’s recursive.
Write it non-recursively.
How would our code change if we just wanted to compute x^n, where x
is a double and n is a non-negative long value?