Wednesday, July 11, 2012

Pointers in C

    All forms of data in C are assigned an address in memory.  This address allows access to the value or values stored inside which may be read or overwritten with new values.  This concept is key for understanding C, so please check out the pages http://pw1.netcom.com/~tjensen/ptr/pointers.htm, http://boredzo.org/pointers/, and http://www.cprogramming.com/tutorial/c/lesson6.html for truly well-done tutorials.  This tutorial will attempt to demonstrate references and arrays, both of which use pointers.

Reference:
    One way to use pointers is to refer to data stored somewhere in memory.  Each memory address has a numeric value and each variable initialized in C is placed in a numbered address.  You can determine the address of a variable by using the reference operator(&) and assigning the numeric address to a variable called a pointer.

    A pointer can have several types: int*, float*, unsigned short*, char*, etc. all of which are the address of the type and not the type itself.  The following example shows how to use pointers to change variables stored in memory:
#include <stdio.h>

//wait_key prototype
int wait_key();

int main() {
    int a = 5; //int a
    int *b; //int pointer b

    b = &a; //b is given address of a
    *b = 3; //address at b given value

    printf("a = %d\n", a); //a changed

    return wait_key();
}

//wait_key definition
int wait_key() {
    getchar();
    return 0;
}
output in prompt:
a = 3



    The int a is given a value of 5 and the int pointer b is given the address, or reference, of a.  The * in front of b is called the de-reference operator and gives access to the value at the pointer's address.  For this example, 3 is assigned to the value where a is stored, which overwrites the 5 value to 3.

    Pointers can also be input for functions.  Any changes made to the value of a pointer in a function can be seen in other functions, similar to a global variable.  Here is an alternative version of the make_rectangle function from the previous post:
#include <stdio.h>

//alternative name for struct rectangle
typedef struct rectangle rectangle;

//wait_key prototype
int wait_key();

//definition of rectangle data type
struct rectangle {
    int width, height;
};

/* pointer p recieves value of r, set inner
variables found at address p */
void make_rectangle(rectangle* p, int w, int h) {
    (*p).width = w;
    p->height = h; // p-> == (*p).
}

//get inner variables at address p, multiply
int get_area(rectangle* p) {
    int area = p->width;
    area *= (*p).height;
    return area;
}

int main() {
    rectangle r;
    int area;

    //use reference of r as input
    make_rectangle(&r, 4, 5);
    //print changed r
    printf("r: %d x %d\n", r.width, r.height);

    area = get_area(&r);
    printf("area = %d\n", area);

    return wait_key();
}

//wait_key definition
int wait_key() {
    getchar();
    return 0;
}
output in prompt:
r: 4 x 5
area = 20


    The function make_rectangle no longer creates and returns a rectangle, instead it modifies an existing rectangle.  A more appropriate name for this version may be set_rectangle, and the name get_area works for the second function since it still returns an integer representing the rectangle area.

    Even though the rectangle r was created in main, its members can be accessed by other functions with the use of a pointer.  This access allows make_rectangle to set the width and height of r, and get_area to use its width and height.

 Array:
    While pointers can be used to access a single value or data type, they can also refer to a collection of many values called an array.  The pointer will refer to the first element of the array and you can access the other elements from it.  Arrays can either be statically or dynamically placed in memory.

    A statically allocated array will remain the same size until the program quits.  The following example will create an array of 5 integers:
#include <stdio.h>

//wait_key prototype
int wait_key();

int main() {
    int index, size;
    int numbers[5]; //five integers

    //assign each integer a value
    for (index = 0; index < 5; index += 1) {
        numbers[index] = 2 * index + 1;
    }

    //make specific changes
    numbers[3] = 6;
    numbers[2] = numbers[3];

    //print values
    for (index = 0; index < 5; index += 1) {
        printf("numbers[%d] = %d\n",
            index, numbers[index]);
    }

    //size of the static array
    size = sizeof(numbers) / sizeof(*numbers);
    printf("# of elements = %d\n", size);

    //numbers points to the first element
    printf("*numbers = %d\n", *numbers);

    return wait_key();
}

//wait_key definition
int wait_key() {
    getchar();
    return 0;
}
output in prompt:
numbers[0] = 1
numbers[1] = 3
numbers[2] = 6
numbers[3] = 6
numbers[4] = 9
# of elements = 5
*numbers = 1


    When created, the variable 'numbers' is an array of five integers with five unspecified values.  A simple for loop from 0 to 4 allows us to initialize each element's value using [ and ] around a number.  This index number is how many spaces from the first element you wish to move: 0 is still the first element and moving 4 over gives us the fifth and final element.

    A quick warning: always track how big your array is and avoid using an index number outside the range 0 to (number of elements - 1).  An index beyond these bounds can access other, irrelevant parts in memory and even change them.  Newer operating systems tend to prevent pointers accessing the memory of other programs, but you want to avoid corrupting your own program's data.  One nice feature of static arrays is the ability to find their size.

    The expression "sizeof(numbers) / sizeof(*numbers)" tells us the number of integers in the array variable 'numbers'.  First, we find the size of the entire array in bytes (the sizeof command).  Since integers require 4 bytes, we also divide by the number of bytes a single element requires.  We could also divide by sizeof(int), but the original expression also finds the number of elements for other types of arrays (double, short, etc.).

    Dynamically allocated arrays require an additional header file called stdlib.h (the C standard library).  This additional library includes the commands malloc and free to enable creating and removing arrays at any point during the program.  Aside from these commands, the syntax is similar to static arrays:
#include <stdio.h>
#include <stdlib.h> //malloc and free

//wait_key prototype
int wait_key();

int main() {
    int index, size;
    int *numbers; //pointer to integer

    numbers = (int*)malloc(5 * sizeof(int));
    size = 5;

    //assign each integer a value
    for (index = 0; index < 5; index += 1) {
        numbers[index] = 2 * index + 1;
    }

    //make specific changes
    numbers[3] = 6;
    numbers[2] = numbers[3];

    //print values
    for (index = 0; index < 5; index += 1) {
        printf("numbers[%d] = %d\n",
            index, numbers[index]);
    }

    //numbers points to the first element
    printf("*numbers = %d\n", *numbers);

    //remove old array from memory
    printf("\nfreeing old memory\n");
    free(numbers);

    //create new array in memory
    printf("using new memory\n");
    size = 7;
    numbers = (int*)malloc(size * sizeof(int));

    //create and print values
    for (index = 0; index < size; index += 1) {
        *(numbers + index) = 3 * index - 7;
        printf("numbers[%d] = %d\n", index,
            numbers[index]);
    } //a[b] == *(a + b)

    free(numbers); //free before exiting

    return wait_key();
}

//wait_key definition
int wait_key() {
    getchar();
    return 0;
}
output in prompt:
numbers[0] = 1
numbers[1] = 3
numbers[2] = 6
numbers[3] = 6
numbers[4] = 9
*numbers = 1

freeing old memory
using new memory
numbers[0] = -7
numbers[1] = -4
numbers[2] = -1
numbers[3] = 2
numbers[4] = 5
numbers[5] = 8
numbers[6] = 11

    This version of the numbers array allows you to create an array during any point in the program, but requires more maintenance.  Unfortunately, the size of this type of array cannot be calculated with the sizeof operation.  The sizeof operation will most likely return 4 or 8 which is the number of bytes required to point to a location in memory, not the number of bytes the data occupies.

    Also, the malloc and free commands must be used.  The malloc command is written as "(data type*)malloc(size * sizeof(data type))" where size is the number of elements and data type can be int, double, etc.  malloc checks for and fills available memory and returns a pointer to the first element.  This memory is not initialized though, so assigning values to each element is recommended.

    Once this memory is allocated with malloc, it remains in memory until free is called.  Using the same pointer malloc returns, free will remove an array and make its memory available for other uses.  If free is not called, the array can remain in memory until the computer is restarted.  It is recommended to compliment any malloc calls with a corresponding free call.

    For this example, the numbers array starts with five elements, but is later removed from memory.  Next, it is given seven new elements and finally removed from memory before the program stops.  Aside from malloc and free, the syntax is very similar to static arrays.  Notice that "*(numbers + index)" works similar to "numbers[index]"; this is because the pointer numbers points to the first element while numbers+1 points to the second element.  These written styles can be used interchangeably.

    Pointers can also be used for strings, which will be the next post.  Thank you for reading!

No comments:

Post a Comment