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 1 Memory: How it looks Address • Let’s start with a snapshot of memory with N bytes 0 1 2 3 N-1 8-bits 2 Layout of char variables • Let’s declare and initialize 2 char variables char char c1 = c2 = c1; c2; ‘A’; /* same as c1 = 65 */ 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 … … Address c1 65 ... ... ... 100 c2 66 200 … … 3 Layout of char variables (cont) … … Address c1 65 ... ... ... 100 c2 66 200 … … char c1; char c2; c1 = ‘A’; /* same as c1 c2 = 66; /* same as c2 printf(“c1: %c, c1: %d, printf(“c2: %c, c2: %d, = 65 */ = ‘B’ */ &c1: %d\n”, c1, c1, &c1); &c2: %d\n”, c2, c2, &c2); Printed output: c1: A, c1: 65, &c1: 100 c2: B, c2: 66, &c2: 200 4 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 1. What will be the address of the object 2. How will we order the bytes in memory 5 (1) Address of Larger Objects short s = 0x1234; int i = 0x12345678; /* 2 bytes long */ /* 4 bytes long */ • We store multi-byte objects contiguously in memory and let the address of the object be the smallest address of the bytes used c1 c2 65 ... ... 66 ... ... s printf(“s: %hx, &s: %d\n”, s, &s); printf(“i: %x, &i: %d\n”, i, &i); Printed output: s: 1234, &s: 300 i: 12345678, &i: 400 … … 0x12 0x34 Address 100 200 300 301 &s 400 401 402 403 &i ... ... i 0x12 0x34 0x56 0x78 … … 6 (2) Byte Order of Larger Objects … … c1 c2 65 ... ... 66 ... ... s 0x34 0x12 100 i … … c1 200 300 301 ... ... 0x78 0x56 0x34 0x12 … … Address 400 401 402 403 Little-endian c2 &s 65 ... ... 66 ... ... s 0x12 0x34 Address 100 200 300 301 &s 400 401 402 403 &i ... ... &i i 0x12 0x34 0x56 0x78 … … Big-endian 7 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 = char *q = short *ps int *pi = • &c1; &c2; = &s; &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 8 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 9 Pointers (Pictorially) • Assume we have the following initializations char *p = char *q = short *ps int *pi = • &c1; &c2; = &s; &i; Then we can depict p, q, ps and pi pointing to variables c1, c2, s and i … … p q ps pi c1 100 c2 200 300 Address 65 ... ... 66 ... ... s 0x34 0x12 100 200 300 301 ... ... 400 i 0x78 0x56 0x34 0x12 400 401 402 403 … … Memory 10 Pointers (Pictorially) … … char *p = char *q = short *ps int *pi = printf(“&c1: %d, p: printf(“&c2: %d, q: printf(“&s: %d, ps: printf(“&i: %d, pi: Printed output: &c1: 100, p: &c2: 200, q: &s: 300, ps: &i: 400, pi: &c1; &c2; = &s; &i; %d\n”, %d\n”, %d\n”, %d\n”, p q &c1, p); &c2, q); &s, ps); &i, pi); ps pi c1 100 200 300 c2 ... ... 66 ... ... s 0x34 0x12 100 200 300 301 ... ... 400 i 100 200 300 400 65 Address 0x78 0x56 0x34 0x12 … … 400 401 402 403 Memory 11 Dereferencing (Indirection) • What if I want to refer to the object pointed to by a pointer – Simply use the dereferencing operator *p = ‘C’; *q = ‘D’; *ps = 5000; *pi = 6000; /* /* /* /* same same same same printf(“c1: %c, *p: printf(“c2: %c, *q: printf(“s: %x, *ps: printf(“d: %x, *pi: Printed output: c1: C, *p: C c2: D, *q: D s: 5000, *ps: 5000 i: 6000, *pi: 6000 as as as as c1 = ‘C’ c2 = ‘D’ s = 5000 I = 6000 %c\n”, %c\n”, %x\n”, %x\n”, */ */ */ */ c1, *p); c2, *q); s, *ps); i, *pi); 12 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’; Address p 100 c1 ‘A’ 100 q 200 c2 ‘B’ 200 Initial Condition Address q = p; /* same as q=&c1 since p=&c1*/ *q = ‘E’; printf(“c1: %c, *p: %c, *q: %c\n”, c1, *p, *q); p 100 c1 ‘E’ 100 q 100 c2 ‘B’ 200 After Assignment 13 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 * 14 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. */ i i 2 0x56 0x34 0x12 400 401 402 403 0xcd 0xab 400 401 402 403 0x34 0x12 15 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’ */ Address pp &p p 200 c1 ‘A’ 100 c2 ‘R’ 200 16 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 17 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?) 18 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 */ 19 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 20 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 */ 21 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 */ 22 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: 2, y: 3 23 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 */ Printed output: x: 3, *p1: 3, y: 2, *p2: 2 x: 3, *p1: 2, y: 2, *p2: 3 24 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… 25 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 */ 26 Pointers - Summary • • Pointers store memory addresses All pointers have the same size – • You take the address of a variable using the addressOf operator (&) – • 4-bytes or 8-bytes &i You can refer to an object using its address using pointer dereferencing operator (*) – – – int *p=&i; *pi = 4; *(&i)=4 27 Pointers - Summary • • • • NULL – means an invalid memory address – means that we do not know the type of object stored at the memory location pointed to by the pointer void * 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 28