Saturday, December 10, 2011

C Programming Lecture 13


Pointers and Arrays
A pointer is a value that designates the address, or location in memory, of some value. There are four fundamental things you need to know about pointers:
  How to declare them
  How to assign to them
  How to reference the value associated with the pointer (dereferencing) and
  How they relate to arrays
We'll also discuss the relationship of pointers with text strings and the more advanced concept of function pointers.
Pointers are variables that hold a memory location ­­ the location of some other variable. One can
access the value of the variable pointed to using the dereferencing operator '*'. Pointers can hold any data type, even functions.
The vast majority of arrays in C are simple lists, also called "1 dimensional arrays". We will briefly
cover multi­dimensional arrays in a later chapter.

Declaring pointers
Consider the following snippet of code which declares two pointers:
struct  MyStruct  {
int        m_aNumber;
float  num2;
};
int                            *  pJ2;
struct  MyStruct  *  pAnItem;
The first four lines define a structure. The next line declares a variable which points to an int, and the bottom line declares a variable which points to something with structure MyStruct. So to declare a variable as something which points to some type, rather than contains some type, the asterisk (*) is placed before the variable name.
In the first of the following lines of code, var1 is a pointer to a long while var2 is a long and not a pointer to a long. In the second line p3 is declared as a pointer to a pointer to an int.
long    *    var1,  var2;
int        **  p3;
Pointer types are often used as parameters to function calls. The following shows how to declare a function which uses a pointer as an argument. Since C passes function arguments by value, in order to allow a function to modify a value from the calling routine, a pointer to the value must be passed. Pointers to structures are also used as function arguments even when nothing in the struct will be modified in the function. This is done to avoid copying the complete contents of the structure onto the stack. More about pointers as function arguments later.
int  MyFunction(  struct  MyStruct  *pStruct  );

Assigning values to pointers
So far we've discussed how to declare pointers. The process of assigning values to pointers is next. To assign a pointer the address of a variable, the & or 'address of' operator is used.


int        myInt;
int      *pPointer;
struct  MyStruct         dvorak;
struct  MyStruct       *pKeyboard;
pPointer  =  &myInt;
pKeyboard  =  &dvorak;

Here, pPointer will now reference myInt and pKeyboard will reference dvorak.
Pointers can also be assigned to reference dynamically allocated memory. The malloc() and calloc() functions are often what are used to do this.
#include  <stdlib.h>

struct  MyStruct  *pKeyboard;

pKeyboard  =  malloc(sizeof(struct  MyStruct));


The malloc function returns a pointer to dynamically allocated memory (or NULL if unsuccessful). The size of this memory will be appropriately sized to contain the MyStruct structure.
The following is an example showing one pointer being assigned to another and of a pointer being assigned a return value from a function.
static  struct  MyStruct  val1,  val2,  val3,  val4;

struct  MyStruct  *ASillyFunction(  int  b  )
{
struct  MyStruct  *myReturn;
if  (b  ==  1)  myReturn  =  &val1;
else  if  (b==2)  myReturn  =  &val2; else  if  (b==3)  myReturn  =  &val3; else  myReturn  =  &val4;
return  myReturn;
}

struct  MyStruct  *strPointer;
int           *c,  *d;
int           j;

c  =  &j;                                                       /*  pointer  assigned  using  &  operator  */
d  =  c;                                                         /*  assign  one  pointer  to  another              */
strPointer  =  ASillyFunction(  3  );  /*  pointer  returned  from  a  function.  */

When returning a pointer from a function, do not return a pointer that points to a value that is local to the function or that is a pointer to a function argument. Pointers to local variables become invalid when the function exits. In the above function, the value returned points to a static variable.
Returning a pointer to dynamically allocated memory is also valid.




Pointer dereferencing

The pointer p points to the variable a.
To access a value to which a pointer points, the * operator is used. Another operator, the ­> operator is used in conjunction with pointers to structures. Here's a short example.
int        c,  d;
int        *pj;
struct  MyStruct  astruct;
struct  MyStruct  *bb;
c  =  10;


pj  =  &c;
d  =  *pj;
pj  =  &d;
*pj  =  12;
bb  =  &astruct;
(*bb).m_aNumber  =  3; bb­>num2  =  44.3;
*pj  =  bb­>m_aNumber;


/*  pj  points  to  c  */
/*  d  is  assigned  the  value  to  which  pj  points,  10  */
/*
  now  points  to  d  */
/*  d  is  now  12  */


/*  assigns  3  to  the  m_aNumber  member  of  astruct  */
/*  assigns  44.3  to  the  num2  member  of  astruct               */
/*  eqivalent  to  d  =  astruct.m_aNumber;                        */


The expression bb­>mem is entirely equivalent to (*bb).mem. They both access the mem element of the structure pointed to by bb. There is one more way of dereferencing a pointer, which will be discussed in the following section.
When dereferencing a pointer that points to an invalid memory location, an error often occurs which results in the program terminating. The error is often reported as a segmentation error. A common cause of this is failure to initialize a pointer before trying to dereference it.
C is known for giving you just enough rope to hang yourself, and pointer dereferencing is a prime example. You are quite free to write code that accesses memory outside that which you have
explicity requested from the system. And many times, that memory may appear as available to your program due to the vagaries of system memory allocation. However, even if 99 executions allow your program to run without fault, that 100th execution may be the time when your "memory
pilfering" is caught by the system and the program fails. Be careful to ensure that your pointer
offsets are within the bounds of allocated memory!
The declaration void *somePointer; is used to declare a pointer of some nonspecified type.
You can assign a value to a void pointer, but you must cast the variable to point to some specified type before you can dereference it. Pointer arithmetic is also not valid with void * pointers.

Pointers and Arrays
Up to now, we've carefully been avoiding discussing arrays in the context of pointers. The
interaction of pointers and arrays can be confusing but here are two fundamental statements about
it:
  A variable declared as an array of some type acts as a pointer to that type. When used by
     
itself, it points to the first element of the array.
  A pointer can be indexed like an array name.



The first case often is seen to occur when an array is passed as an argument to a function. The
function declares the parameter as a pointer, but the actual argument may be the name of an array.
The second case often occurs when accessing dynamically allocated memory. Let's look at examples
of each. In the following code, the call to calloc() effectively allocates an array of struct MyStruct
items.
float  KrazyFunction(  struct  MyStruct  *parm1,  int  p1size,  int  bb  )
{
int  ix;
for  (ix=0;  ix<p1size;  ix++)  {
if  (parm1[ix].m_aNumber  ==  bb  )
       
return  parm1[ix].num2;
}
return  0.0f;
}

struct  MyStruct  myArray[4];
#define  MY_ARRAY_SIZE  (sizeof(myArray)/sizeof(struct  MyStruct)) float  v3;
struct  MyStruct  *secondArray;
int       someSize;
int       ix;
/*  initialization  of  myArray  ...  */
v3  =  KrazyFunction(  myArray,  MY_ARRAY_SIZE,  4  );
secondArray  =  calloc(  someSize,  sizeof(struct  MyStruct)); for  (ix=0;  ix<someSize;  ix++)  {
secondArray[i].m_aNumber  =  ix  *2;
secondArray[i].num2  =  .304  *  ix  *  ix;
}
Pointers and array names can pretty much be used interchangably. There are exceptions. You cannot
assign a new pointer value to an array name. The array name will always point to the first element
of the array. In the function KrazyFunction above, you could however assign a new value to
parm1, as it is just a copy of the value for myArray. It is also valid for a function to return a pointer
to one of the array elements from an array passed as an argument to a function. A function should
never
 return a pointer to a local variable, even though the compiler will probably not complain.
When declaring parameters to functions, declaring an array variable without a size is equivalent to declaring a pointer. Often this is done to emphasize the fact that the pointer variable will be used in a manner equivalent to an array.
/*  two  equivalent  function  definitions  */
int  LittleFunction(  int  *paramN  );
int  LittleFunction(  int  paramN[]  );

Now we're ready to discuss pointer arithmetic. You can add and subtract integer values to/from
pointers. If myArray is declared to be some type of array, the expression *(myArray+j), where j is an integer, is equivalent to myArray[j]. So for instance in the above example where we had the expression secondArray[i].num2, we could have written that as *(secondArray+i).num2 or more simply (secondArray+i)­>num2.
Note that for addition and subtraction of integers and pointers, the value of the pointer is not
adjusted by the integer amount, but is adjusted by the amount multiplied by the size (in bytes) of the
type
 to which the pointer refers. One pointer may also be subtracted from another, provided they
point to elements of the same array (or the position just beyond the end of the array). If you have a
pointer
 that points to an element of an array, the index of the element is the result when the array name is subtracted from the pointer. Here's an example.
struct  MyStruct  someArray[20];
struct  MyStruct  *p2;
int  idx;

/*  array  initialiation  ..  */
for  (p2  =  someArray;  p2  <  someArray+20;          ++p2)  {
if  (p2­>num2  >  testValue)  break;
}
idx  =  p2  ­  someArray;
You may be wondering how pointers and multidimensional arrays interact. Lets look at this a bit in
detail. Suppose A is declared as a two dimensional array of floats (float  A[D1][D2];) and that
pf is declared a pointer to a float. If pf is initialized to point to A[0][0], then *(pf+1) is equivalent to
A[0][1] and *(pf+D2) is equivalent to A[1][0]. The elements of the array are stored in row­major
order.
float  A[6][8];
float  *pf;
pf  =  &A[0][0];
*(pf+1)  =  1.3;          /*  assigns  1.3  to  A[0][1]  */
*(pf+8)  =  2.3;          /*  assigns  2.3  to  A[1][0]  */
Let's look at a slightly different problem. We want to have an two dimensonal array, but we don't
need to have all the rows the same length. What we do is declare an array of pointers. The second
line below declares A as an array of pointers. Each pointer points to an float. Here's some applicable
code:
float    linearA[30]; float  *A[6];



A[0]  =  linearA;
A[1]  =  linearA  +  5;
A[2]  =  linearA  +  11;
A[3]
  =  linearA  +  15;
A[4]
  =  linearA  +  21;
A[5]
  =  linearA  +  25;
A[3][2]  =  3.66;
A[3][­3]  =  1.44;


/*     5 ­  0  =  5  elements  in  row                           */
/*  11  ­  5  =  6  elements  in  row          */
/*  15  ­  11  =  4  elements  in  row  */
/*  21  ­  15  =  5  elements                     */
/*  25  ­  21  =  4  elements                     */
/*  30  ­  25  =  5  elements                     */
/*  assigns  3.66  to  linear[17];                  */
/*  refers  to  linear[12];
negative  indices  are  sometimes  useful.  */



We also note here something curious about array indexing. Suppose myArray is an array and idx is an integer value. The expression myArray[idx] is equivalent to idx[myArray]. The first is equivalent to *(myArray+idx), and the second is equivalent to *(idx+myArray). These turn out to be the same, since the addition is commutative.
Pointers can be used with preincrement or post decrement, which is sometimes done within a loop, as in the following example. The increment and decrement applies to the pointer, not to the object to which the pointer refers. In other words, *pArray++ is equivalent to *(pArray++).
long    myArray[20];
long    *pArray;
int    i;

/*  Assign  values  to  the  entries  of  myArray  */ pArray  =  myArray;
for  (i=0;  i<10;  ++i)  {






*pArray++  =  5  +  3*i  +  12*i*i;
 
*pArray++  =  6  +  2*i  +  7*i*i;  }

Pointers in Function Arguments
Often we need to invoke a function with an argument that is itself is a pointer. In many instances,
the
 variable is itself a parameter for the current function and may be a pointer to some type of
structure. The ampersand character is not needed in this circumstance to obtain a pointer value, as
the variable is itself a pointer. In the example below, the variable pStruct, a pointer, is a
parameter
 to function FunctTwo, and is passed as an argument to FunctOne. The second
parameter to FunctOne is an int. Since in function FunctTwo,  mValue is a pointer to an int,
the pointer must first be dereferenced using the * operator, hence the second argument in the call is
*mValue. The third parameter to function FunctOne is a pointer to a long. Since pAA is itself a
pointer to a long, no ampersand is needed when it is used as the third argument to the function.
int  FunctOne(  struct  SomeStruct  *pValue,  int  iValue,  long  *lValue  )
{
/*    do  some  stuff  ...  */
return  0;
}
int  FunctTwo(  struct  someStruct  *pStruct,  int  *mValue  )
{
int  j;
long    AnArray[25]; long  *pAA;
pAA  =  &AnArray[13];
j  =  FunctOne(  pStruct,  *mValue,  pAA  ); return  j;
}

Pointers and Text Strings
Historically, text strings in C have been implemented as arrays of characters, with the last character in the string being a zero, or the NULL character. Most C implementations come with a standard
library of functions for manipulating strings. Many of the more commonly used functions expect the strings to be null terminated strings of characters. To use these functions requires the inclusion of
the standard C header file "string.h".
A statically declared, initialized string would look similar to the following:
static  const  char  *myFormat  =  "Total  Amount  Due:  %d";

The variable myFormat can be viewed as an array of 21 characters. There is an implied null
character ('\0') tacked on to the end of the string after the 'd' as the 21st item in the array. You can also initialize the individual characters of the array as follows:
static  const  char  myFlower[]  =  {  'P',  'e',  't',  'u',  'n',  'i',  'a',  '\0'  };

An initialized array of strings would typically be done as follows:
static  const  char  *myColors[]  =  {
"Red",  "Orange",  "Yellow",  "Green",  "Blue",  "Violet"  };

The initilization of an especially long string can be split across lines of source code as follows.






static  char  *longString  =  "Hello.  My  name  is  Rudolph  and  I  work  as  a  reindeer  "
"around
  Christmas  time  up  at  the  North  Pole.    My  boss  is  a  really  swell  guy."
    "
  He  likes  to  give  everybody  gifts.";


The library functions that are used with strings are discussed in a later chapter.

Pointers to Functions
C also allows you to create pointers to functions. Pointers to functions can get rather messy.
Declaring a typedef to a function pointer generally clarifies the code. Here's an example that uses a
function pointer, and a void * pointer to implement what's known as a callback. The
DoSomethingNice function invokes a caller supplied function TalkJive with caller data. Note
that
 DoSomethingNice really doesn't know anything about what dataPointerrefers to.
typedef    int  (*MyFunctionType)(  int,  void  *);                     /*  a  typedef  for  a  function
pointer  */
int  DoSomethingNice(  int  aVariable,  MyFunctionType  aFunction,  void *dataPointer  )
{
int  rv  =  0;
if  (aVariable  <  THE_BIGGEST)  {
/*  invoke  function  through  function  pointer  (old  style)  */ rv  =  (*aFunction)(aVariable,  dataPointer  );
/*  invoke  function  through  function  pointer  (new  style)  */ rv  =  aFunction(aVariable,  dataPointer  );
}
return  rv;
}

struct  sDataINeed  {
int          colorSpec;
char      *phrase;
}
typedef  struct  sDataINeed    DataINeed;

int  TalkJive(  int  myNumber,  void  *someStuff  )
{
/*  recast  void  *  to  pointer  type  specifically  needed  for  this  function  */ DataINeed  *myData  =  someStuff;
/*  talk  jive.  */
return  5;
}
static  DataINeed    sillyStuff  =  {  BLUE,  "Whatcha  talkin  'bout  Willis?"  }; DoSomethingNice(  41,  &TalkJive,    &sillyStuff  );
Some versions of C may not require an ampersand preceeding the TalkJive argument in the
DoSomethingNice call. Some implementations may require specifically casting the argument to
the MyFunctionType type, even though the function signature exacly matches that of the
typedef.
Function pointers can be useful for implementing a form of polymorphism in C. First one declares a
structure having as elements function pointers for the various operations to that can be specified
polymorphically. A second base object structure containing a pointer to the previous structure is
also
 declared. A class is defined by extending the second structure with the data specific for the class, and static variable of the type of the first structure, containing the addresses of the functions that are associated with the class. This type of polymorphism is used in the standard library when file I/O functions are called.
A similar mechanism can also be used for implementing a state machine in C. A structure is defined which contains function pointers for handling events that may occur within state, and for functions to be invoked upon entry to and exit from the state. An instance of this structure corresponds to a
state.
 Each state is initialized with pointers to functions appropriate for the state. The current state of the state machine is in effect a pointer to one of these states. Changing the value of the current state pointer effectively changes the current state. When some event occurs, the appropriate function is
called through a function pointer in the current state. 

0 comments: