View Single Post
Old Apr 28th, 2005, 7:14 PM   #1
Stack Overflow
Newbie
 
Stack Overflow's Avatar
 
Join Date: Feb 2005
Posts: 9
Rep Power: 0 Stack Overflow is on a distinguished road
Send a message via AIM to Stack Overflow
Pointers in C (Part II)

Pointers in C: Part II of III
Written by Stack Overflow

Introduction
You have learned most of the basics pertaining pointers in the previous tutorial. Our goal in this tutorial is to discuss the fact-finding situations of pointers and their importance. This tutorial is geared toward assisting you to fully understand pointers in application.

All tutorial examples are Standard C compliant, and should build successfully on any C compiler. All examples are optimized for the -Wall, -pedantic, -ansi, and -std=c89 GCC flags.

Before we start, I think it would be advantageous to learn the rules of Pointers.

The 3 Rules of Pointers
Rule #1: A pointer to void may be converted to or from a pointer to any incomplete or object type. A pointer to any incomplete or object type may be converted to a pointer to void and back again; the result shall compare equal to the original pointer.

Rule #2: A pointer to a non-q-qualified type may be converted to a pointer to the q-qualified version of the type; the values stored in the original and converted pointers shall compare equal.

Rule #3: An integral constant expression with the value 0, or such an expression cast to type void *, is called a null pointer constant. If a null pointer constant is assigned to or compared for equality to a pointer, the constant is converted to a pointer of that type. Such a pointer, called a null pointer, is guaranteed to compare unequal to a pointer to any object or function. Two null pointers, converted through possibly different sequences of casts to pointer types, shall compare equal.

Pointers in application
This section of the tutorial is here to help you fully understand the concept of pointers. Once it becomes clear, you will feel free of doubt and be able to use pointers—in any situation—to your advantage.

Pointer arithmetic is crucial, as it will lead to a more successful approach of pointer utilization.

For our first approach we will examine a piece of code:
#include <string.h>

int main() {
	/* local variables */
	char ch[10];
	char *p;

	/* p points to ch */
	p = &ch[0];

	/* copy data to p */
	strcpy(p, "Test");
	/* append text to p */
	memcpy(&p[4], "ing", 3);
	/* end p here */
	p[7] = '\0';

	/* end program */
	return 0;
}
Example 1.1: Utilizing pointers

As seen before when you send a variable that points to an object—a pointer—to a function, you don't send its address. Since the function—memcpy()—expects a variable to a pointer, you'd generally pass the pointer; in this case p. If we were to dissect this behavior we may notice the first parameter does more than just that; at least while normal variable names were sent. This particular function—memcpy()—requests a one-dimensional pointer in one or more of it's arguments. We are aware that the & operator only applies to objects in memory.

With our current knowledge of pointers, we should understand that the pointer p points to the local character array variable ch. If either of them were to have changed, the result will affect both variables involved. In addition to the fact that “p[4]” is a single character from our array; the 4th location. In the cataclysmic event, we are complying with the one-dimensional array the function requested as to this extent: We are sending the memory address—&—of the object p at position 4. Any changes to this variable will proceed with position 4.

In the next example, you will see the pointer unary operators used:
/* function prototypes */
int myFunc(int *);

int main() {
	/* local variables */
	int i = 3;
	int j = 2;
	int *k;

	/* k now points to j */
	k = &j;

	myFunc(&i);   /* i isn't a pointer, so send variables address */
	myFunc(k);    /* k points to j, k is already a pointer; don't send address */

	/* end program */
	return 0;
}

/* Pass a pointer and return its value */
int myFunc(int *x) {
	/* local variable */
	int z = *x;
	/* return value */
	return z;
}
Example 1.2: Using the operators

In this particular example, we passed a standard variable and a pointer to our new function. One of the first operations we execute is that of pointing a pointer to a local variables' memory address. Which that can be done by preceding the variable identifier by an ampersand sign (&), which depicts "address of".

Further down, we pass the memory address of another variable—i—to our function. The function expects one pointer-to-integer argument. By utilizing the unary operator—&—we can successfully pass our local variable as a pointer. The second call to our function, we send a pointer-to-integer variable which points to another local variable.

Inside our function—myFunc(int *)—we use the unary dereferencing operator, *, to obtain the object's value of the pointer passed. When applied to a pointer, it accesses the object the pointer points to. Using a pointer we can directly access the value stored in the variable pointed by it just by preceding the pointer identifier with the dereference operator asterisk (*), that can be literally translated to "value pointed by".

A common mistake by programmers today is that they don't fully understand the complete concept of pointers. With all that you have learned, I'm sure you could spot the mistake in the following code:
#include <stdio.h>

int main() {
	/* local variables */
	char ch[25];
	char *p;

	/* p points to ch */
	p = &ch[0];

	/* scan in string from input stream */
	scanf("%s", &p);

	/* end program */
	return 0;
}
Example 1.3: Spotting a common mistake

I see this done on countless occasions. By now, you'll probably spot out that scanf() contains the error. According to scanf(), it's arguments must be pointers: If you want to store the result of a scanf operation on a standard variable you should precede it with the reference operator, i.e. an ampersand sign (&). To say the least, p is not a standard variable. It is already a pointer. Passing the address of p as an scanf() argument is a common mistake. It's like sending the address of of the address ch. To break it down, p points to ch; p is the memory address of the object ch. As &p is the address of the reference to p. Let me show by example:
#include <stdio.h>

int main() {
	/* local variables */
	char c[10];
	char *p;

	/* p points to the memory address of c */
	p = &c[0];

	/* print */
	printf("Address of c: %p\n", c);
	printf("Address of p: %p\n", p);
	printf("Address of reference to p: %p\n", &p);

	/* end program */	
	return 0;
}
Example 1.4: The address of

If you compile and run this program, you will notice that p has the memory address of c. While the address of p is different than the address referenced to p. We can accomplish passing a pointer to scanf() by passing the pointer itself. As seen above, the arguments of scanf() must be pointers.

The following example shows the difference between array and non-array pointer usage:
int main() {
	/* local variables */
	int i, j[2];
	int *p, *q;

	/* p points to i */
	p = &i;
	/* q points to j */
	q = &j[0];

	/* change p */
	*p = 3;		  /* i = 3 */
	/* change q */
	*++(q) = 1;	/* j[1] = 1 */

	/* end program */
	return 0;
}
Example 1.5: The difference between array's and non array's

You may notice when we used pointer arithmetic when we changed q. To fully understand this, you'll need to know the operator precedence rules:

Operator        Precedence        Category
&, *            Highest (first)   Unary operators
* / div mod     Second            Multiplying operators
+ -             Third             Adding operators
< <> < > <= >=  Lowest (last)     Relational operators
As seen, the Unary Operator has the highest precedence. The reason our equation changes j[1] and not j[0] is simple. It's because the dereference operator takes precedence over the arithmetic operators; in this case the arithmetic needs be done first. First, pointer arithmetic must be done on q, and then the result is dereferenced. If the expression was *q++, q would be dereferenced, and then the result would be incremented in regular arithmetic. Which would result in j[0] being set to 1 rather than j[1].

Pointer Techniques
Pointers can be used in many programs, varying from the instance. You can use them to allocate memory, point to other variables, change data from a specified address, and so on and so forth. It really depends on where you use them, and what they are being used for. When attempting to improve your pointer techniques, think as if you are optimizing your source code for performance.

One example is that of passing a 2-dimensional pointer to a function and allocating memory to it there. It would most likely be advantageous to pass the pointer's memory address, create a temporary variable inside the function to allocate the memory to it, and point the passed pointer to your newly created local temporary variable.

Here is a code example to follow:
#include <stdlib.h>

/* function prototype */
void alloc2D(char ***, int, int);

int main() {
	/* local variables */
	int i;
	char **string = NULL;

	/* Allocate memory; Pass memory address of pointer */
	alloc2D(&string, 3, 5);

	/* Free memory */
	for (i = 0; i < 3; i++) {
		free(string[i]);
		string[i] = NULL;
	}
	free(string);
	string = NULL;

	/* end program */
	return 0;
}

/* Pass memory here for allocation */
void alloc2D(char ***str, int row, int column) {
	/* local variables */
	int i;
	char **temp = NULL;

	/* Allocate */
	temp = malloc(row * sizeof *temp);
	/* If allocation succeeded */
	if (temp != NULL)
		/* Loop through rows */
		for (i = 0; i < row; i++)
			/* Allocate rows with size of columns */
			temp[i] = malloc(column * sizeof *temp);

	/* Point passed object to local pointer */
	*str = temp;
}
Example 2.1: Allocating a 2-dimensional pointer.

Keep in mind this function shows the basic concept and logic behind the explanation. It isn't the most perfect piece of code, and could certainly use some tweaking. As you may have noticed, we pointed the object of str to temp once we finished allocating to our temporary variable. It was easier for us to do it that way so we could just point our passed variable to a new block of memory without having to touch the variable often.

Next, I think it would be good to know why C uses pointers and how to avoid common mistakes.

The 3 Ways C Uses Pointers
Way #1: To create dynamic data structures —— data structures built up from blocks of memory allocated from the heap at run-time.

Way #2: To handle variable parameters passed to functions.

Way #3: To provide an alternative way to access information stored in arrays. Pointer techniques are especially valuable when you work with strings.

You will also find that pointers make code slightly more efficient.

The 3 Common Bugs When Using Pointers
Bug #1: Un-initialized pointers
» To try to reference the value of a pointer even though the pointer is un-initialized and does not yet point to a valid address.

Example:
int *p;

*p = 12;
/*
** The pointer p is un-initialized and points to 
** a random location in memory when you declare it.
** It could be pointing into the system stack, or the 
** global variables, or into the program's code space, 
** or into the operating system.
** When the system interprets *p = 12, the program will 
** simply try to write a 12 to whatever random location 
** p points to.
*/
Bug #2: Invalid Pointer References
» An invalid pointer reference occurs when a pointer's value is referenced even though the pointer doesn't point to a valid block.

Example:
p = q; /* where q is un-initialized */
/*
** The pointer p will then become un-initialized as well,
** and any reference to *p is an invalid pointer reference.
*/
Bug #3: Zero Pointer Reference
» A zero pointer reference occurs whenever a pointer pointing to zero is used in a statement that attempts to reference a block.

Example:
int *p;

p = 0;
*p = 12;
/*
** There is no block pointed to by p.
** Therefore, trying to read or write anything from 
** or to that block is an invalid zero pointer reference.
*/
On a last note, the 3 rules of Pointers are very crucial and explicitly explain what pointers are capable of. All you have to do is implement them, and watch out for any of the 3 bugs. With that known, and the concept of pointers explained, you should be able to use pointers knowing how they operate. My goal in the last tutorial will be focused more on examples rather than explaining the fundamentals. Part I explained the basics, as the sequel—Part II—answers alot of unanswered questions.

Conclusion
That brings this tutorial to an end. In the next and last tutorial we will discuss methods to dynamically allocate an any-dimensional pointer, and explain contiguous allocation. I had planned to show 4-dimensional allocation in this tutorial, but I thought it would be best served in the next.



Stack Overflow is offline   Reply With Quote