class: center, middle ### COMP26020 Programming Languages and Paradigms Part 1: C Programming *** # Pointers Applications --- # Allow a Function to Access the Calling Context - Can a function modify the variables passed to it as arguments? **Not directly: in C, arguments are passed by value (i.e. copied)** ```c void increment(int param) { param++; } int main(int argc, char **argv) { int x = 20; printf("before call, x is %d\n", x); // prints 20 increment(x); printf("after call, x is %d\n", x); // prints 20 return 0; } ``` .codelink[
`10-pointers-applications/params-copy.c`
] --
--- name: params # Allow a Function to Access the Calling Context - Instead, a function can take **pointer parameters and access/modify the pointed-to objects** ```c *void increment(int *param) { * (*param)++; } int main(int argc, char **argv) { int x = 20; printf("before call, x is %d\n", x); // prints 20 * increment(&x); printf("after call, x is %d\n", x); // prints 21 return 0; } ``` .codelink[
`10-pointers-applications/params-pointer.c`
] --- template: params
--- template: params
--- template: params
--- name:return # Allow a Function to Access the Calling Context - Want a function to 'return' more than a single value? ```c // we want this function to "return" 3 things: the product and the (integer) quotient // of n1 by n2, as well as an error code in case division is impossible int multiply_and_divide(int n1, int n2, int *product, int *division) { if(n2 == 0) return -1; // Can't divide if n2 is 0 *product = n1 * n2; *division = n1 / n2; return 0; } int main(int argc, char **argv) { int p, d, a = 10, b = 5; if(multiply_and_divide(a, b, &p, &d) == 0) { printf("10*5 = %d\n", p); printf("10/5 = %d\n", d); } } ``` .codelink[
`10-pointers-applications/params-pointers.c`
] --- template: return
--- template: return
--- name: large1 # Efficient Function Calls with Large Data Structures - Assume we want to write a function updating a large struct variable ```c typedef struct { // lots of large (8-byte) fields: double a; double b; double c; double d; double e; double f; } large_struct; large_struct f(large_struct s) { // entire struct is copied on call and return! s.a += 42.0; return s; } int main(int argc, char **argv) { large_struct x = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0}; large_struct y = f(x); printf("y.a: %f\n", y.a); } ``` .codelink[
`10-pointers-applications/large-param-copy.c`
] --- template: large1
??? - Now let's see what happens in memory: at the beginning we have the large struct x in main's memory - It is large: 6 fields times 8 bytes, so 48 bytes in total --- template: large1
--- template: large1
--- template: large1
--- .leftcol[ ```c typedef struct { double a; double b; double c; double d; double e; double f; } large_struct; large_struct f(large_struct s) { s.a += 42.0; return s; } int main(int argc, char **argv) { large_struct x = {1, 2, 3, 4, 5, 6}; large_struct y = f(x); printf("y.a: %f\n", y.a); return 0; } ``` - Code disassembled with `objdump --source` (after compilation with `gcc -g`) - **Every copy costs time and memory: significant overhead for large structs!** ] .rightcol[ ```asm int main(int argc, char **argv) { /* ... */ large_struct y = f(x); 11e9: 48 8d 45 a0 lea -0x60(%rbp),%rax 11ed: ff 75 f8 pushq -0x8(%rbp) 11f0: ff 75 f0 pushq -0x10(%rbp) 11f3: ff 75 e8 pushq -0x18(%rbp) 11f6: ff 75 e0 pushq -0x20(%rbp) 11f9: ff 75 d8 pushq -0x28(%rbp) 11fc: ff 75 d0 pushq -0x30(%rbp) 11ff: 48 89 c7 mov %rax,%rdi 1202: e8 2e ff ff ff callq 1135
1207: 48 83 c4 30 add $0x30,%rsp /* ... */ } ``` ```asm large_struct f(large_struct s) { /* ... */ return s; 1153: 48 8b 45 f8 mov -0x8(%rbp),%rax 1157: 48 8b 55 10 mov 0x10(%rbp),%rdx 115b: 48 8b 4d 18 mov 0x18(%rbp),%rcx 115f: 48 89 10 mov %rdx,(%rax) 1162: 48 89 48 08 mov %rcx,0x8(%rax) 1166: 48 8b 55 20 mov 0x20(%rbp),%rdx 116a: 48 8b 4d 28 mov 0x28(%rbp),%rcx 116e: 48 89 50 10 mov %rdx,0x10(%rax) 1172: 48 89 48 18 mov %rcx,0x18(%rax) 1176: 48 8b 55 30 mov 0x30(%rbp),%rdx 117a: 48 8b 4d 38 mov 0x38(%rbp),%rcx 117e: 48 89 50 20 mov %rdx,0x20(%rax) 1182: 48 89 48 28 mov %rcx,0x28(%rax) } ``` ] --- name: large2 # Efficient Function Calls with Large Data Structures - With a pointer: only a single copy of the variable is maintained ```c typedef struct { double a; double b; double c; double d; double e; double f;} large_struct; *void f(large_struct *s) { // now takes a pointer parameter * (*s).a += 42.0; // dereference s to access x return; } int main(int argc, char **argv) { large_struct x = {1, 2, 3, 4, 5, 6}; * f(&x); // pass x's address printf("x.a: %f\n", x.a); return 0; } ``` .codelink[
`10-pointers-applications/large-param-pointer.c`
] --- template: large2
--- template: large2
--- template: large2
--- template: large2
--- .leftcol[ ```c typedef struct { double a; double b; double c; double d; double e; double f; } large_struct; void f(large_struct *s) { (*s).a += 42.0; return; } int main(int argc, char **argv) { large_struct x = {1, 2, 3, 4, 5, 6}; f(&x); printf("x.a: %f\n", x.a); return 0; } ``` ] .rightcol[ ```asm int main(int argc, char **argv) { /* ... */ f(&x); 11b9: 48 8d 45 d0 lea -0x30(%rbp),%rax 11bd: 48 89 c7 mov %rax,%rdi 11c0: e8 70 ff ff ff callq 1135
/* ... */ ``` ```asm void f(large_struct *s) { /* ... */ return; 1159: 90 nop } 115a: 5d pop %rbp 115b: c3 retq ``` ] - **With a pointer, only an address is copied: the code is much faster and more memory-efficient** ??? - If we look at the disassembled code for the function call and return operation, it is much shorter --- class: middle, center, inverse # Miscellaneous Pointer-related Topics ??? - Let's see a few miscellaneous pointer-related topics --- # C Arrays and Pointers - Arrays can be accessed with either the bracket or the pointer notation - To pass an array to a function, use a pointer to the array's element type ```c void negate_int_array(int *ptr, int size) { // function taking pointer as parameter for(int i=0; i
`10-pointers-applications/arrays-pointers.c`
] ??? - Arrays and pointers are closely related, although they are not the same thing - In most expressions (e.g. when passed to a function), an array's name is automatically converted into a pointer to its first element - The array itself is therefore never copied when passed to a function, which is good because arrays can be quite large - Here is a function that takes an int pointer as parameter - Notice how we call it: we just use the name of an array variable - Also notice how we can use square brackets on a pointer variable to index it like an array - This function negates all the elements of an int array; if we try it out we can see that it compiles and works fine - Note that C functions cannot return arrays, and a function should never return a pointer to one of its local arrays: that array no longer exists once the function returns --- # C Arrays and Pointers ```c int int_array[2] = {1, 2}; double double_array[2] = {4.2, 2.4}; char char_array[] = "ab"; printf("int_array[0] = %d\n", int_array[0]); printf("int_array[1] = %d\n", int_array[1]); printf("*(int_array+0) = %d\n", *(int_array+0)); // pointer arithmetic! printf("*(int_array+1) = %d\n", *(int_array+1)); // +1 means + sizeof(int) bytes printf("double_array[0] = %f\n", double_array[0]); printf("double_array[1] = %f\n", double_array[1]); printf("*(double_array+0) = %f\n", *(double_array+0)); printf("*(double_array+1) = %f\n", *(double_array+1)); printf("char_array[0] = %c\n", char_array[0]); printf("char_array[1] = %c\n", char_array[1]); printf("*(char_array+0) = %c\n", *(char_array+0)); printf("*(char_array+1) = %c\n", *(char_array+1)); ``` .codelink[
`10-pointers-applications/arrays-pointers-indexing.c`
]
--- # Pointers and Structures ```c typedef struct { int int_member1; int int_member2; int *ptr_member; } my_struct; my_struct ms; // members are set through p below my_struct *p = &ms; (*p).int_member1 = 1; // don't forget the parentheses! . takes precedence over * p->int_member2 = 2; // p->x is a shortcut for (*p).x p->ptr_member = &(p->int_member2); printf("p->int_member1 = %d\n", p->int_member1); printf("p->int_member2 = %d\n", p->int_member2); printf("p->ptr_member = %p\n", p->ptr_member); printf("*(p->ptr_member) = %d\n", *(p->ptr_member)); ``` .codelink[
`10-pointers-applications/pointers-structs.c`
] --
??? - As previously seen, we often pass structs through pointers - Furthermore, structs themselves can have pointer members - Let's take an example here with a struct having two integer members, int_member1 and int_member2, and an int pointer ptr_member - We can create a pointer to a previously declared struct variable - To access an int member we have two choices - The classic star operator for dereferencing, followed by the dot for member access - Don't forget the parentheses, as the dot takes precedence over the star - There is also a shortcut, the arrow: it can be used on a struct pointer to access a member; it first dereferences the pointer, then accesses the member - We can get the address of an individual struct member with the ampersand operator - Here we set the pointer member equal to the address of one of the integer members - Then we print the value of each member, and we dereference the pointer member - In memory things look like this - Let's assume the struct variable is at address 0x150 - The struct pointer p points there - Remember that the members are placed in memory in declaration order (the compiler may add padding between them, but there is none here) - So the address of int_member2 is equal to the start address plus the size of int_member1, which is 4 bytes on our machine because it is an int - So it is 0x154 - And this is what is contained in the pointer member --- # Pointer Chains - A pointer variable is itself an object stored in memory, so it has its own address - So it can be pointed to! ```c int value = 42; // integer int *ptr1 = &value; // pointer to int printf("ptr1: %p, *ptr1: %d\n", ptr1, *ptr1); // ``` .codelink[
`10-pointers-applications/pointer-chains.c`
]
??? - One last thing: we can create pointers to pointers and construct chains - Here we have a value and we create a first pointer to it - Then we have a second pointer pointing to the first one - The type of this pointer to pointer is int star star, which means pointer to pointer to int - We also create a pointer to pointer to pointer to int, int star star star - Next we print the pointer chains - So in memory we get something like this - The value is pointed to by ptr1, which is pointed to by ptr2, which itself is pointed to by ptr3 - Note in the code the use of several star operators for dereferencing some pointers - For example star star ptr2 means: access the object pointed to by the pointer that ptr2 points to - In other words, a first star gives us access to ptr1, and a second one to value - Pointers to pointers are useful, for example, to create dynamically allocated multidimensional arrays, as we will see in the next video --- # Pointer Chains - A pointer variable is itself an object stored in memory, so it has its own address - So it can be pointed to! ```c int value = 42; // integer int *ptr1 = &value; // pointer to int int **ptr2 = &ptr1; // pointer to pointer to int printf("ptr1: %p, *ptr1: %d\n", ptr1, *ptr1); printf("ptr2: %p, *ptr2: %p, **ptr2: %d\n", ptr2, *ptr2, **ptr2); // ``` .codelink[
`10-pointers-applications/pointer-chains.c`
]
--- # Pointer Chains - A pointer variable is itself an object stored in memory, so it has its own address - So it can be pointed to! ```c int value = 42; // integer int *ptr1 = &value; // pointer to int int **ptr2 = &ptr1; // pointer to pointer to int int ***ptr3 = &ptr2; // pointer to pointer to pointer to int printf("ptr1: %p, *ptr1: %d\n", ptr1, *ptr1); printf("ptr2: %p, *ptr2: %p, **ptr2: %d\n", ptr2, *ptr2, **ptr2); printf("ptr3: %p, *ptr3: %p, **ptr3: %p, ***ptr3: %d\n", ptr3, *ptr3, **ptr3, ***ptr3); ``` .codelink[
`10-pointers-applications/pointer-chains.c`
]
--- # Summary - Pointers use cases - Allow a function to modify variables in its calling context - Allow a function to 'return' more than a single value - Avoid costly data copies on function calls - Relationship of arrays and structures with pointers - Pointer chains ---- .center[Feedback form: https://bit.ly/2WZ60QG]