Pointers
The Pointer Defined • int *x; • Read as: declare x as a pointer to a 32-bit integer • Interpretation: declare x as a variable on the stack that holds the numeric address of the location in memory at which are 32 bits that we intend to manipulate as a signed integer • sizeof(x) is the word-size of the machine in bytes • on a 32 bit machine that can address 2^32 bytes of memory, 4 • on a 64 bit machine, 8 • etc • sizeof(*x) is 4, or sizeof(int)
Pointer Dereferencing • There are two fundamental ways to manipulate or obtain (dereference) memory through this pointer: • *x • Read as: dereference x • Interpretation: set(if left of equals) or get(if right of equals) 32 bits of memory interpreted as a signed integer beginning at the memory address contained in variable x
Pointer Dereferencing Continued • x[i] • Read as: the ( i + 1) st element of array x (indices start at 0 ) • Interpretation: set or get 32 bits of memory interpreted as a signed integer beginning at the memory address computed as address + (i * sizeof(type)) • For all examples that follow, assume x contains the value 10000 , i.e. x refers to the 10000 th byte of memory • x[5] will manipulate or obtain the 32 bit signed integer beginning at address (10000 + (5 * 4)) , or 10020
Arrays Versus Pointers • int x[256]; • This is a declaration of 256 contiguous 4-byte integers on the stack (1024 bytes total) • The identifier x is a pointer to an integer that points to this stack location, and it can never point anywhere else • int *x; • This is a declaration of a pointer to an integer that does not refer to any location until it is assigned a value (for example using the & operator or via malloc) • It can point to any address in memory, stack or heap, and its value can be changed dynamically • RHS use of either identifier x operates almost identically; use either as a pointer to one-to-many integers, or deference either to obtain an single integer • Can’t put the array -char- pointer on the LHS (i.e. can’t say x = something , but can say x[index] = something ) (this makes sense if you think about it) • Compiler generates instructions to compute the exact stack-pointer-relative address for any chosen element of the array when using an array; it can do this because it knows it’s contiguous • Compiler generates instructions to compute the pointer-value-relative address for any chosen element of the array when using a pointer
10000 0 1st 10004 1 Array Storage 2nd 2 10008 3rd 10012 3 4th x[5]=100; 10016 4 100 5th 5 10020 6th 10000 6 10024 int *x; 7th 10028 7 8th 10032 8 9th 10036 9 10th 10040 10 11th 10044 11
Implications of the Pointer-type • The CPU *does not* make any assumptions about how to interpret any piece of memory, you must explicitly tell it how to do so • The compiler does all pointer arithmetic using the guidance you gave it via the type you declared that the pointer points to • The compiler chooses machine instructions to generate for the CPU based upon the type you declared that the pointer points to
Implications of the Pointer-Type continued • Previous example demonstrated operations on 4 byte values and pointer arithmetic by 4-byte values because an int is represented as 4 bytes • If x were declared as: char *x; • Operations on 1 byte signed integers • *x refers to address 10000 • x[5] refers to address 10005 • If x were declared as: short *x; • Operations on 2 byte signed integers • *x refers to address 10000 • x[5] refers to address 10010 • If x were declared as unsigned long long *x; • Operations on 8 byte unsigned integers • *x refers to address 10000 • x[5] refers to address 10040
Pointer Manipulation • C allows you to “add - to” or “subtract - from” a pointer by using mathematical operators + and – • C doesn’t add the addend to the address directly, but performs the following computation: • address = address + (addend * sizeof(type)) • Interpretation: when you add one to a pointer, it causes the pointer to point to the address of the next contiguous piece of memory of sizeof(type) • Semantic equivalent: were x pointing to the i th element of an array in memory, it’s the equivalent of setting x to point to the (i+1) st element • As discussed, dereferencing an array causes a comparable computation: • &x[i] = x + (i * sizeof(type)
Pointers to Pointers • int **x; • Read as: declare x as a pointer to a pointer to a 32-bit integer • Interpretation: declare x as a variable on the stack that holds the numeric address at which is another numeric address at which are 32 bits that we intend to manipulate as a signed integer • sizeof(x) is the word size of the machine in bytes • sizeof (*x) is also the word size of the machine (it’s an int* ) • sizeof(**x) is 4, or sizeof(int)
Multi-Dimensional Arrays Versus Pointers • int x[256][10]; • This is a declaration of a contiguous pool of stack memory that we intend to interpret as 256 “rows” of 10 “columns”, each holding a 4-byte integer for a total of 256*10*4 = 10240 bytes of memory. • The identifier x is a pointer to a pointer to an integer that points to the beginning of this pool on the stack, and it can never point anywhere else • int **x; • This is a declaration of a pointer to a pointer to an integer that does not refer to any location until it is assigned a value (for example using the & operator or via malloc) • It can point to any address in memory, stack or heap, and its value can be changed dynamically • RHS use of either identifier x operates almost identically; use either as a pointer to pointer to one-to-many integers, or deference either twice to obtain an single integer • Can’t put the array-char-p2p on the LHS (i.e. can’t say x = something , but can say x[index] = something ) • Compiler generates instructions to compute the exact stack-pointer-relative address for any chosen element of the array when using an array; it can do this because it knows it’s contiguous • Compiler generates instructions to compute the pointer-value-relative addresses for any chosen element of the array when using a pointer
Stack-Based Multi-Dimensional 10000 10004 10008 10012 10016 10020 0 1 2 3 4 5 Array Storage 1st 2nd 3rd 4th 5th 6th 10000 10024 7th 8th 9th 10th 11th 12th int x[4][6]; 10048 13th 14th 15th 16th 17th 18th 10072 19th 20th 21st 22nd 23rd 24th Allocated & deallocated automatically via stack advancement & retreat Total storage of a 3x5 array of integers is (3x5) * 4 100 x[3][2] = 100;
10000 0 Heap-Based 1st 10004 1 Multi-Dimensional 2nd 2 10008 Array Storage 3rd 10012 3 4th 10016 4 x[5][2] = 100 5th 100000 5 10020 100 int *x; 6th 10000 6 10024 int **x; 7th 0 1 2 3 4 5 10028 7 8th 1st 2nd 3rd 4th 5th 10032 8 9th 100000 100004 100008 100012 100016 100020 Must be allocated iteratively via malloc 10036 9 Must be freed iteratively via free 10th Total storage of a 3x5 array of integers is (3x(5+1)) * 4 10040 10 11th 10044 11
Pointer Casting • int i = 5; • char *y = (char *)(&i); • Declares i as an integer on the stack and puts 5 in that location, then declares y as a pointer to a character and assigns the address of i casted (interpreted as) to a pointer to a character to it. • &i and y both have the same numeric value pointing to the same piece of memory • Dereferencing y hereafter generates instructions that operate on chars at i’s memory address instead of integers
Pointer Casting Example enum integral_type case ITYPE_SHORT: { // doubles the 16 bit value found at pointer ITYPE_CHAR = 0, *((short *)pointer_to_integral_type) *= 2; ITYPE_SHORT, break; ITYPE_INT, case ITYPE_INT: ITYPE_LONGLONG // doubles the 32 bit value found at pointer }; *((int *)pointer_to_integral_type) *= 2; void doublevalue(void *pointer_to_integral_type, enum integral_type data_type) break; { case ITYPE_LONGLONG: if(pointer_to_integral_type == NULL) // doubles the 64 bit value found at pointer return; *((long long *)pointer_to_integral_type) *= 2; switch(data_type) break; { default: case ITYPE_CHAR: // doubles the 8-bit value found at pointer return; *((char *)pointer_to_integral_type) *= 2; } break; }
Recommend
More recommend