280 likes | 399 Views
Today’s Material. Layout of Data Objects in Memory Memory: How it looks Address and byte-ordering of variables Little-endian/Big-endian conventions Pointers AddressOf (&) operator Dereferencing void * Changing Function Arguments through Pointers Function Pointers. Memory: How it looks.
E N D
Today’s Material • Layout of Data Objects in Memory • Memory: How it looks • Address and byte-ordering of variables • Little-endian/Big-endian conventions • Pointers • AddressOf (&) operator • Dereferencing • void * • Changing Function Arguments through Pointers • Function Pointers
Memory: How it looks • Let’s start with a snapshot of memory with N bytes Address 0 1 2 3 N-1 8-bits
Address … … c1 100 65 ... ... ... c2 200 66 … … Layout of char variables • Let’s declare and initialize 2 char variables char c1; char c2; c1 = ‘A’; /* same as c1 = 65 */ c2 = 66; /* same as c2 = ‘B’ */ • Recall that a char variable occupies 1 byte in memory • So here is how c1&c2 will look like in memory • We are assuming that c1 is stored at address 100 and c2 is stored at address 200
Printed output: c1: A, c1: 65, &c1: 100 c2: B, c2: 66, &c2: 200 Layout of char variables (cont) … … Address c1 100 65 ... ... ... c2 200 66 … … char c1; char c2; c1 = ‘A’; /* same as c1 = 65 */ c2 = 66; /* same as c2 = ‘B’ */ printf(“c1: %c, c1: %d, &c1: %d\n”, c1, c1, &c1); printf(“c2: %c, c2: %d, &c2: %d\n”, c2, c2, &c2);
Layout of Larger Objects • What about objects that span 2 or more bytes? • short – 2 bytes • int – 4 bytes • long – 4 or 8 bytes • float – 4 bytes • double – 8 bytes • For these objects, 2 conventions must be established • What will be the address of the object • How will we order the bytes in memory
… … Address c1 100 65 ... ... c2 200 66 ... ... &s 300 0x12 s 301 0x34 ... ... &i 0x12 400 401 0x34 i Printed output: 0x56 402 s: 1234, &s: 300 i: 12345678, &i: 400 0x78 403 … … (1) Address of Larger Objects • We store multi-byte objects contiguously in memory and let the address of the object be the smallest address of the bytes used short s = 0x1234; /* 2 bytes long */ int i = 0x12345678; /* 4 bytes long */ printf(“s: %hx, &s: %d\n”, s, &s); printf(“i: %x, &i: %d\n”, i, &i);
… … … … Address Address c1 c1 100 100 65 65 ... ... ... ... c2 c2 200 200 66 66 ... ... ... ... &s &s 300 300 0x12 0x34 s s 301 301 0x12 0x34 ... ... ... ... &i &i 0x78 0x12 400 400 401 401 0x34 0x56 i i 0x34 0x56 402 402 0x78 0x12 403 403 … … … … (2) Byte Order of Larger Objects Big-endian Little-endian
Pointers • What if I want to store the address of an object in a variable and refer to the object through its address? • Enter pointer variables char *p = &c1; char *q = &c2; short *ps = &s; int *pi = &i; • Pointers are like any other variables in that they store values • But the value they store is the address of another variable, that is, a memory address • p = 100, q = 200, ps = 300, pi = 400
Pointers (more) • How many bytes does a pointer occupy in memory? • On 32-bit machines, 4-bytes • On 64-bit machines, 8-bytes • Since the content of a pointer is a memory address, we can visualize a pointer pointing to the data object whose address it contains • Next slide for illustration
p 100 q 200 ps 300 pi 400 Pointers (Pictorially) • Assume we have the following initializations … … … … Address Address c1 c1 100 100 65 65 ... ... ... ... c2 c2 200 200 66 66 char *p = &c1; char *q = &c2; short *ps = &s; int *pi = &i; ... ... ... ... 300 300 0x34 0x34 s s 301 301 0x12 0x12 ... ... ... ... • Then we can depict p, q, ps and pi pointing to variables c1, c2, s and i 0x78 0x78 400 400 401 401 0x56 0x56 i i 0x34 0x34 402 402 0x12 0x12 403 403 … … … … Memory
Printed output: &c1: 100, p: 100 &c2: 200, q: 200 &s: 300, ps: 300 &i: 400, pi: 400 Pointers (Pictorially) … … … … Address Address char *p = &c1; char *q = &c2; short *ps = &s; int *pi = &i; p c1 c1 100 100 65 65 100 ... ... ... ... q c2 c2 200 200 66 66 200 ... ... ... ... ps 300 300 0x34 0x34 300 s s printf(“&c1: %d, p: %d\n”, &c1, p); printf(“&c2: %d, q: %d\n”, &c2, q); printf(“&s: %d, ps: %d\n”, &s, ps); printf(“&i: %d, pi: %d\n”, &i, pi); 301 301 0x12 0x12 ... ... ... ... pi 400 0x78 0x78 400 400 401 401 0x56 0x56 i 0x34 0x34 402 402 0x12 0x12 403 403 … … … … Memory
Printed output: c1: C, *p: C c2: D, *q: D s: 5000, *ps: 5000 i: 6000, *pi: 6000 Dereferencing (Indirection) • What if I want to refer to the object pointed to by a pointer • Simply use the dereferencing operator *p = ‘C’; /* same as c1 = ‘C’ */ *q = ‘D’; /* same as c2 = ‘D’ */ *ps = 5000; /* same as s = 5000 */ *pi = 6000; /* same as I = 6000 */ printf(“c1: %c, *p: %c\n”, c1, *p); printf(“c2: %c, *q: %c\n”, c2, *q); printf(“s: %x, *ps: %x\n”, s, *ps); printf(“d: %x, *pi: %x\n”, i, *pi);
Address Address p c1 100 ‘A’ 100 q ‘B’ c2 200 200 Initial Condition Address Address p c1 100 ‘E’ 100 q ‘B’ c2 200 100 After Assignment Pointer Assignment • We can make a pointer point to another object after initialization • Consider the following example: char *p = &c1; char *q = &c2; c1 = ‘A’; *q = ‘B’; q = p; /* same as q=&c1 since p=&c1*/ *q = ‘E’; printf(“c1: %c, *p: %c, *q: %c\n”, c1, *p, *q);
Pointers (Recap) • Pointers store memory addresses • All pointers have the same size • 4-bytes or 8-bytes • The type of the pointer (char *, int *) is only a hint to the compiler • It tells the compiler the type of object stored at the memory location • This allows the compiler to access as many bytes as the type of object that the pointer points to during dereferencing • *pi = 1; modifies 4 bytes since pi’s type is int * • *p = ‘D’ modifies 1 byte since p’s type is char *
2 400 400 401 401 0x56 i 0x34 402 402 0x12 403 403 0xcd 400 400 401 401 0xab i 402 402 0x34 403 403 0x12 Pointers (Example) p = (char *)&i; /* same as p = (char *)pi; *p = 2; /* Changes only the first byte of the integer I to 2. It does not change the other 3 bytes */ ps = (short *)&i; *ps = 0xabcd; /* Changes the first 2 bytes of integer i to 0xabcd. The last 2 bytes are not changed. */
Address p pp c1 100 ‘A’ 200 &p c2 200 ‘R’ Pointers to Pointers • Just like other objects, pointers have memory addresses • This means that I can have another pointer contain the memory address of a pointer • This would be called a pointer to a pointer char c1 = ‘A’, c2 = ‘B’; char *p = &c1; char **pp = NULL; pp = &p; *pp = &c2; /* make p point to c2. Same as p=&c2 */ **pp = ‘R’; /* same as *p = ‘R’ or c2 = ‘R’ */
Alignment • Some architectures require an integer/float variable to start at a 4-byte boundary • Otherwise the hardware cannot access the variable • So when you dereference a pointer, make sure that the proper alignment is available • For x86, alignment is not required • For Sparc, alignment is required • For example, assume that a char variable has address 102, which is not divisible by 4 • Typecasting this address to a int * and dereferencing it will work on a x86 machine, but will crash your program in a sparc machine
void * • Sometimes you do not know the type of object stored at a memory location • So you want to declare a generic pointer • This is where void * is used void *pv = NULL; pv = (void *)&v; /* assign pv the address of v */ • You cannot dereference a void * pointer • That is, *pv = 8 is invalid • The reason is obvious: The compiler does not know the type of object stored at the memory location pointed to by “pv”. • So the compiler does not know how many bytes to access with *pv (can be 1 byte, 2 bytes, 4 bytes?)
Dereferencing void * • To dereference a void *, you need to first typecast it to a known pointer type • This allows the compiler to determine how many bytes to access char *p = NULL; short *ps = NULL; p = (char *)pv; *p = 65; /* Changes 1 byte at the address pointed to by p & pv */ ps = (short *)pv; *ps = 65; /* Changes 2 bytes at the address pointed to by ps & pv */ /* Typecast first, dereference later */ *((int *)pv) = 70; /* changes 4 bytes at the address pointed to by pv */
Dereferencing: Final Note • The bottom line with dereferencing is • When you dereference a pointer, you must let the compiler know the type of object you are referring to • That is the compiler must know the size of the object being referred to • The pointer type gives this information to the compiler • Without knowing the object type, the compiler cannot generate correct code because dereferencing an untyped pointer would be ambiguous
Functions and Pointers • Recall from our discussion on functions that function arguments are passed by value. • That is, function arguments are copied to the corresponding function parameter • Any changes to the parameter within the function does not affect the argument void decompose(float x, int int_part, float frac_part){ int_part = (int)x; frac_part = x – int_part; } void main(void){ int i = 0; float f = 0.0; decompose(2.35, i, f); printf(“int_part: %d, frac_part: %.2f\n”, i, f); } /* Prints: int_part: 0, frac_part: 0.00 */
Changing Arguments within Functions • How do we change arguments i&f within function decompose then? • The idea is to pass the addresses of i&f (instead of their values) and to refer to i&f through their addresses by pointer dereferencing void decompose(float x, int *p_int_part, float *p_frac_part){ *p_int_part = (int)x; *p_frac_part = x – *p_int_part; } /* end-decompose */ void main(void){ int i = 0; float f = 0.0; decompose(2.35, &i, &f); printf(“int_part: %d, frac_part: %.2f\n”, i, f); } /* end-main */ /* Prints: i: 2, f: 0.35 */
Printed output: x: 2, y: 3 Changing Arguments within Functions • This way, we can change any argument within a function by passing its address and referring to the argument by pointer dereferencing /* swaps the contents of 2 ints */ void swap(int *a, int *b){ int tmp; tmp = *a; *a = *b; *b = tmp; } /* end-swap */ void main(void){ int x=3, y=2; swap(&x, &y); printf(“x: %d, y: %d\n”, x, y); } /* end-main */
Printed output: x: 3, *p1: 3, y: 2, *p2: 2 x: 3, *p1: 2, y: 2, *p2: 3 Changing Arguments within Functions /* swaps the contents of 2 pointers */ void swap(int **a, int **b){ int *tmp; tmp = *a; *a = *b; *b = tmp; } /* end-swap */ void main(void){ int x=3, y=2; int *p1=&x, *p2=&y; printf(“x: %d, *p1: %d, y: %d, *p2: %d\n”, x, *p1, y, *p2); swap(&p1, &p2); printf(“x: %d, *p1: %d, y: %d, *p2: %d\n”, x, *p1, y, *p2); } /* end-main */
Function Pointers • C allows you to declare pointers to functions & to invoke those functions through the pointer • The address of a function is the name of the function itself • Next slide for an example…
Function Pointers: Example int Add(int x, int y){return x+y;} int Subtract(int x, int y){return x-y;} int Multiply(int x, int y){return x*y;} void main(void){ /*fptr is a function pointer to functions with prototype F(int, int)*/ int (*fptr)(int, int); int x; fptr = Add; x = fptr(3, 2); printf(“fptr: %p, Add: %p, x: %d\n”, fptr, Add, x); fptr = Subtract; x = fptr(3, 2); printf(“fptr: %p, Subtract: %p, x: %d\n”, fptr, Subtract, x); fptr = Multiply; x = fptr(3, 2); printf(“fptr: %p, Multiply: %p, x: %d\n”, fptr, Multiply, x); } /* end-main */
Pointers - Summary • Pointers store memory addresses • All pointers have the same size • 4-bytes or 8-bytes • You take the address of a variable using the addressOf operator (&) • &i • You can refer to an object using its address using pointer dereferencing operator (*) • int *p=&i; • *pi = 4; • *(&i)=4
Pointers - Summary • NULL • means an invalid memory address • void * • means that we do not know the type of object stored at the memory location pointed to by the pointer • Changing Arguments Within Functions • To change the value of an argument within a function, pass the argument’s address instead of its value and refer to the argument inside the function by pointer dereferencing • Function Pointers • A function’s name is its address • You can invoke functions by function pointers