Representing Stacks in C

A stack is an ordered collection of objects in memory. C already has a data type that is an ordered collection – the array. We can therefore use the array to represent a stack; however, a drawback to this approach is that the number of items in an array is fixed at declaration. A stack, on the other hand, is a dynamically sized collection that grows and shrinks as items are pushed onto the stack and popped off of the stack.

There is more than one way to represent a stack in C. We can use the array to store a stack, as long as we declare the array to be large enough to store the maximum number of items we think will be placed on the stack. The start of the array functions as the bottom of the stack, and the last element in the array that holds data will be the top the stack that shifts as data is popped off the stack and pushed on the stack.

We can therefore declare a stack to be a structure containing an array as well an integer value indicating the top of the stack. If we declare the array as itself being of the struct type, we can declare a stack that contains different value types.

const int stack_array_size = 100;

enum StackArrayNodeType {integer, floatingpoint, character};

struct StackArrayNode {
	enum StackArrayNodeType;

	union {
		int iValue;
		double dValue;
		char cValue;
	};
};

struct StackArray {
	int top;
	struct StackArrayNode items[stack_array_size];
}; 

The top field in the StackArray structure is declared as an int, since it always represents an index or offset. We represent an empty stack by setting the top field to -1 or some other negative value. We can even set up a simply function to check if the stack is empty or not.

We need to declare a set of auxiliary functions for handling the nodes that make up item in the stack; remember here that each node is itself a stack consisting of an enumerated type identifier as well as a union allowing for different types of actual data stored.

//copy node after it has been popped off the stack
struct StackArrayNode *CopyNode(struct StackArrayNode source) {
	struct StackArrayNode *ReturnNode = (StackArrayNode*)malloc(sizeof(StackArrayNode));
	ReturnNode->type = source.type;
	switch (source.type) {
	case integer:
		ReturnNode->iValue = source.iValue;
		break;
	case floatingpoint:
		ReturnNode->dValue = source.dValue;
		break;
	case character:
		ReturnNode->cValue = source.cValue;
		break;
	}
	return ReturnNode;
}

//returns true only if able to identify type and print value to the screen
bool StackArrayNodePrint(const struct StackArrayNode *theNode) {
	bool returnValue = false;
	//check for NULL node
	if (theNode != NULL) {
		//print node according to its type
		switch (theNode->type) {
		case integer:
			printf("Type: Int \t Value: %d\n", theNode->iValue);
			returnValue = true;
			break;
		case floatingpoint:
			printf("Type: Double \t Value: %f\n", theNode->dValue);
			returnValue = true;
			break;
		case character:
			printf("Type: Char \t Value: %c\n", theNode->cValue);
			returnValue = true;
			break;
		}
	}
	return returnValue;
}

bool StackArrayNodeSetInt(struct StackArrayNode *theNode, int value) {
	bool returnValue = false;
	if (theNode->type == integer) {
		theNode->iValue = value;
		returnValue = true;
	}
	return returnValue;
}

bool StackArrayNodeSetChar(struct StackArrayNode *theNode, char value) {
	bool returnValue = false;
	if (theNode->type == character) {
		theNode->cValue = value;
		returnValue = true;
	}
	return returnValue;
}

bool StackArrayNodeSetFloatingPoint(struct StackArrayNode *theNode, double value) {
	bool returnValue = false;
	if (theNode->type == floatingpoint) {
		theNode->dValue = value;
		returnValue = true;
	}
	return returnValue;
}

We have three functions, each for setting a different value type for the node. We also have a function that prints the node depending on its type. This is necessary since the printf() function requires different conversion characters in the string literal.

bool StackArrayIsEmpty(const struct StackArray theStack) {
	if (theStack->top < 0) {
		return true;
	}
	else {
		return false;
	}
}

Using simple functions like this may impose a minuscule performance penalty as function stacks are added, but it adds immeasurably to the readability of programs. Let’s also add a function to clear the stack. We do this by simply setting the stack’s top value to -1.

void ClearStack(struct StackArray *theStack) {
	theStack->top = -1;
}

Next, let’s implement the pop function. The possibility of underflow should be addressed when implementing the pop functionality for a stack. In this context, we should make sure that the stack is not empty before popping off a value. This function will return a pointer to a dynamically allocated stack object that stores the copied data from the original item on the stack.

struct StackArrayNode *StackArrayPop(struct StackArray *theStack) {

	struct StackArrayNode *returnValue = NULL;

	if (StackArrayIsEmpty(theStack) == false) {
		//note that the decremement of the top index should occur after popping the value
		//because after push operation the index is set to the last element added
		returnValue = CopyNode(theStack->items[theStack->top--]);
	}

	return returnValue;
}

For implementing the push() operation, let’s first add a helper function to check if the stack is already full.

bool StackArrayIsFull(const struct StackArray *theStack) {
	if (theStack->top >= stack_array_size) {
		return true;
	}
	else {
		return false;
	}
}

The push() operation must increment the top of the stack by one. In our case, the stack will increment the top of the stack before actually adding the value, using the prefix increment operator. We do this since whenever the the stack is cleared or initialized, the top value is set to -1, indicating an empty stack. Since we cannot have an array with a negative index value, we must increment it first so that the value to be used as the index is at least 0.

Since we have three different value types, we will have three different push() functions – one for the int type, one for the double type, and one for the char type.

bool StackArrayPushInt(struct StackArray *theStack, int value) {
	//check if stack is full
	bool returnValue = StackArrayIsFull(theStack);
	//if stack isn't full, push value onto stack
	if (returnValue == false) {
		//guard against a negative value other than -1
		if (theStack->top < -1) { 			
                     theStack->top = -1;
		}
		//increment top index before adding value 
		++theStack->top;
		theStack->items[theStack->top].type = integer;
		returnValue = StackArrayNodeSetInt(&theStack->items[theStack->top], value);
	}
	return returnValue;
}

bool StackArrayPushDouble(struct StackArray *theStack, double value) {
	//check if stack is full
	bool returnValue = StackArrayIsFull(theStack);
	//if stack isn't full, push value onto stack
	if (returnValue == false) {
		//guard against a negative value other than -1
		if (theStack->top < -1) { 			
                    theStack->top = -1;
		}
		++theStack->top;
		theStack->items[theStack->top].type = floatingpoint;
		returnValue = StackArrayNodeSetFloatingPoint(&theStack->items[theStack->top], value);
	}
	return returnValue;
}

bool StackArrayPushChar(struct StackArray *theStack, char value) {
	//check if stack is full
	bool returnValue = StackArrayIsFull(theStack);
	//if stack isn't full, push value onto stack
	if (returnValue == false) {
		//guard against a negative value other than -1
		if (theStack->top < -1) { 			
                      theStack->top = -1;
		}
		//increment top index before adding value 
		++theStack->top;
		theStack->items[theStack->top].type = character;
		returnValue = StackArrayNodeSetChar(&theStack->items[theStack->top], value);
	}
	return returnValue;
}

Finally, we can bring it all together in the main() function, were we utilize everything we have written.

int main()
{
	struct StackArray theStack; 
	struct StackArrayNode *tempNode = NULL;

	ClearStack(&theStack);

	StackArrayPushInt(&theStack, 42);
	StackArrayPushChar(&theStack, 'X');
	StackArrayPushDouble(&theStack, 1.61803);
	StackArrayPushInt(&theStack, 8088);
	StackArrayPushChar(&theStack, 'H');
	StackArrayPushDouble(&theStack, 3.14159);
	StackArrayPushInt(&theStack, 1138);
	StackArrayPushChar(&theStack, 'T');
	StackArrayPushInt(&theStack, 6174);

	do {
		tempNode = StackArrayPop(&theStack);
		StackArrayNodePrint(tempNode);
		free(tempNode);
	} while (tempNode != NULL);

    return 0;
}

 

Function Pointers

A function is the address of the entry point to a group of instructions bundled together into the function construct. A function pointer stores the entry-point address of a function. If we dereference a function pointer, it is the same as calling the function.

When declaring a function pointer we need to separate the pointer declaration from the type of the return value. To separate the two we wrap the pointer declaration in a set of parentheses.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

long addNums(int a, int b);
long subNums(int a, int b);
long multNums(int a, int b);

char *reverseStr(char *str);

int main()
{
    char *strOrig = "Greetings, Professor Falken";
    char *str = NULL;

    int i = 0;
    long answer = 0;
    int iVarOne = 42;
    int iVarTwo = 73;

    long (*mathFunction)(int a, int b);

    long (*mathArray[3])(int a, int b);

    char *(*stringFunction)(char *str);

    stringFunction = reverseStr;

    printf("Address of string reverse function is %p\n", reverseStr);
    str = (*stringFunction)(strOrig);

    printf("%s backwards is %s\n", strOrig, str);

    mathArray[0] = addNums;
    mathArray[1] = subNums;
    mathArray[2] = multNums;

    for(i = 0; i < 3; i++){
        answer = (*mathArray[i])(iVarOne, iVarTwo);
        printf("%ld\n", answer);
    }

    return 0;
}

long addNums(int a, int b){
    return a + b;
}

long subNums(int a, int b){
    return a - b;
}

long multNums(int a, int b){
    return a * b;
}

char *reverseStr(char *str){
    int i, j, strLength;

    char *returnString = NULL;

    if(str != NULL){
        strLength = strlen(str);
        returnString = (char *)malloc(sizeof(char) * (strLength  + 1));
        if(returnString != NULL){
            for(i = 0, j = strLength - 1; i < strLength; i++, j--){
                returnString[i] = str[j];
            }
            returnString[strLength] = '\0';
        }
    }
    return returnString;
}

The function names act as address tags in the same way that array names act as address tags.

Pointers to Pointers, Structures, Pointers to Structures

Pointers to pointers can be used to effectively create and manipulate dynamic two dimensional arrays. Arrays of pointers are extremely useful tools for rapid manipulation of large amounts of data.

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define SMALL_BUFFER 256

int readInt(char *buffer, int bounds);
int readPositiveInt(char *buffer, int bounds);
char *readString(char *buffer, int bounds);
void printResults(char **students, int **tests, int numStudents, int numTests);

int main()
{
	int **grades = NULL;
	char **students = NULL;

	int numStudents = 0;
	int numTests = 0;

	int i = 0;
	int j = 0;

	char buffer[SMALL_BUFFER];

	printf("Please enter the number of students ");
	numStudents = readPositiveInt(buffer, SMALL_BUFFER);

	printf("There are %d students.\n", numStudents);


	printf("Please enter the number of tests ");
	numTests = readPositiveInt(buffer, SMALL_BUFFER);

	printf("There were %d tests.\n", numTests);

	//malloc the space for the student names
	students = (char **)malloc(sizeof(char *) * numStudents);

	//malloc the space for the student grades
	grades = (int **)malloc(sizeof(int *) * numTests);

	for (i = 0; i < numStudents; i++) {
		//flush the output buffer of stdin
		fflush(stdin);
		printf("Please enter student #%d's name ", i+1);
		students[i] = readString(buffer, SMALL_BUFFER);

		printf("Please enter the test scores for student %s \n", students[i]);
		//malloc space for the int array
		grades[i] = (int *)malloc(sizeof(int) * numTests);
		for (j = 0; j < numTests; j++) {
			printf("Enter %s's grade for the %d test", students[i], j + 1);
			grades[i][j] = readPositiveInt(buffer, SMALL_BUFFER);
		}
	}

	printResults(students, grades, numStudents, numTests);

    return 0;
}

void printResults(char **students, int **tests, int numStudents, int numTests) {
	for (int i = 0; i < numTests; i++) {
		printf("\nResults for Test %d: \n", i + 1);
		for (int j = 0; j < numStudents; j++) {
			//*(tests + j) gets the column
			printf("%s scored %d\n", *(students + j), *(*(tests + j) + i));
		}
	}
}

int readInt(char *buffer, int bounds) {
	fgets(buffer, bounds, stdin);
	return atoi(buffer);
}

//ensure a positive number is given
int readPositiveInt(char *buffer, int bounds) {
	int rValue;
	while ((rValue = readInt(buffer, bounds)) < 1) { 		
                 printf("Value must be a positive number. Please enter again: "); 
	} 	
return rValue; 
} 

//read a string from standard input 
char *readString(char *buffer, int bounds) { 	
            fflush(stdin); 	
            fgets(buffer, bounds, stdin); 	
            int strLength = strlen(buffer) - 1; 	
            //allocate a string of the exact length needed 	
           char *rValue = (char *)malloc(sizeof(char) * strLength); 	
            //copy the string over backwards from the buffer 	
            *(rValue + strLength) = '\0'; 	
          while (--strLength >= 0) {
		*(rValue + strLength) = *(buffer + strLength);
	  }
	return rValue;
}

Interesting enough, the argv portion of the command line arguments is a pointer pointer as well.

int main(int argc, char *argv[])
{
	
	int i, j;

	char **myArgs = argv;

	//print out arguments as strings
	for (i = 0; i < argc; i++) {
		printf("argument %d = %s\n", i + 1, *(myArgs + i));
	}


    return 0;
}

Structures are aggregate data types, which means that they can store different data types in the same unit. These units, or records, can hold multiple different data types grouped together and accessed via the structure’s name. We can access these different data elements using the dot operator.

const int char_buffer_size = 128;

struct employee {
	int id;
	char name[char_buffer_size];
	int age;
};

void changeName(struct employee *emp);

int main(int argc, char *argv[])
{
	
	struct employee nd = { 411, "Nick Danger", 32 };

	struct employee *empPtr;

	printf("The starting memory address of nd is %p\n", &nd);
	empPtr = &nd;

	printf("empPtr holds %p\n", empPtr);

	printf("His name is %s, third eye.\n", nd.name);
	printf("His name is %s, third eye.\n", empPtr->name);

	changeName(&nd);

	printf("His name is now %s\n", nd.name);
	printf("His name is now %s\n", empPtr->name);

    return 0;
}

void changeName(struct employee *emp) {
	strcpy_s(emp->name, char_buffer_size, "Ivo Shandor");
}

 

Declaring, Assigning, and Dereferencing Pointers

Pointers are basically the black magic that most of the other programming languages don’t want us to know about, or at least not be able to access directly. There’s good reason for this, actually, but let’s live dangerously.

Pointers are memory locations that store addresses. Pointers are not a data type, although they usually have the data type indicated for the purposes of dereferencing.

A data type is a memory location that can store an element taken from a set of possible values. For example, the int type can store a whole number selected from the range of whole numbers that is delimited for us in the limits.h header file.

#include <stdio.h>
#include <stdlib.h>

int main()
{
 //let's declare some basic data types
 char chValue;
 int iValue;
 double dValue;

 //now let's declare a pointer
 //we set the data type of the variable
 //the pointer is meant to point to
 //and put an * in front of the variable name
 char *chPtr;
 int *iPtr;
 double *dPtr;

 return 0;
}

To create a pointer, we simply put an asterisk, *, in front of the pointer’s variable name.

We cannot store data of a data type in a pointer. We can only store memory addresses in a pointer. To access the memory address of a variable, we use the address-of operator, &.

#include <stdio.h>
#include <stdlib.h>

int main()
{
 double dValue = 19.99;
 int iValue = 266613336;

 //to assign a memory location to a pointer
 //we use the assignment operator and the address-of 
 //operator
 double *dPtr = &dValue;
 int *iPtr = &iValue;

 printf("The address of dValue is %p\n", &dValue);
 printf("The dPtr stores the address %p\n", dPtr);

 printf("The address of iValue is %p\n", &iValue);
 printf("The address stored in iPtr is %p\n", iPtr);

 return 0;
}

The dereference operator is also an asterisk, *, which is the same character used to declare a pointer. The asterisk when placed in a declaration creates a pointer, when used with that same pointer outside the declaration, it tells the program to fetch the value stored in the memory location indicated by the pointer, rather than the memory location itself.

#include <stdio.h>
#include <stdlib.h>

int main()
{
 //declare and initialize int variable
 int iValue = 1138;
 //declare and initialize int pointer
 int *iPtr = &iValue;

 //access address using address-of operator
 printf("%d (%p)\n", iValue, &iValue);
 //access value using dereference operator
 printf("%d (%p)\n", *iPtr, iPtr);

 return 0;
}

The compiler needs to know the type of the variable the pointer points to in order to know how many bytes to read.

As we have seen, a variable is named variable location that can store a value of a certain type. Each variable consists of two parts – the value that it stores, and the memory location it stores the value at. The variable’s address in memory is called its lvalue, and the variable’s content is called its rvalue. The lvalue is what is to the left of the assignment operator, the “bucket”, and the rvalue is what is to the right of the assignment operator, “what fills the bucket”.

With functions in C that pass function arguments by value, the rvalue of the argument is copied onto the function stack. With functions in C that pass function arguments by value, the lvalue of the argument is copied onto the function stack. Passing by reference enables the function to modify the values it receives as parameters and have those changes persist beyond the lifetime of the function. Passing by reference also saves memory, since pointers are always of uniform size, which is typically either 32 or 64 bits.

#include <stdio.h>
#include <stdlib.h>


void swap(int *a, int *b) {
	printf("a points to %p and b points to %p\n", a, b);
	printf("but a is at %p and b is at %p\n", &a, &b);
	int temp = *a;
	*a = *b;
	*b = temp;
}

int main()
{
	int iA = 73;
	int iB = 1337;

	printf("iA = %d and iB = %d\n", iA, iB);
	printf("iA is at %p and b is at %p\n", &iA, &iB);

	//use address-of operator to pass in 
	//variable addresses as arguments
	swap(&iA, &iB);

	printf("Now iA = %d and iB = %d\n", iA, iB);

    return 0;
}

In fact, all functions in C pass values by value, in that a separate, distinct copy is made on the function’s stack, and exists solely for the lifetime of the function. However, when we pass by reference, we create a copy of the argument’s memory location, and not the value the argument is storing. In this way, we can modify variables that have been declared outside the called function.

Pointers By Example in C

Pointer variables are variables that contain addresses of other variables; they contain the location of regular data variables, in effect pointing to the data because they hold that data’s location.

Pointers offer a powerful and efficient means of accessing and changing data.

Pointers follow all the normal naming rules of regular variables. We must define a pointer before we use it; there are character pointers, floating-point pointers, integer pointers, and so on.

There are two pointer operators in C, the address of operator, &, and the dereferencing operator, *. These operators are overloaded operators, since they can be used for other things, such as multiplication.  When used in the context of pointers, the address of operator always produces the memory address of whatever variable it is placed in front of.

#include <stdio.h>


int main(void){

    int a = 9;
    int b = 10;
    char c = 's';
    double d = 19.99;

    printf("%d located at %p\n", a, &a);
    printf("%d located at %p\n", b, &b);
    printf("%c located at %p\n", c, &c);
    printf("%f located at %p\n", d, &d);

    return 0;    

}

Note that the %p conversion specifier is used to print memory addresses.

To define a pointer variable, we follow the same recipe for defining a variable of the desired type, except we play a * between the type name and the variable’s identifier. Because of the addition of the dereferencing operator, the C compiler knows that we wish to define a pointer. A pointer variable must point to a variable of the same type.

#include <stdio.h>


int main(void){

    double *dPtr;
    int *iPtr;

    int i= 73;
    double d = 25.1;

    //assign memory addresses
    //to the pointers
    dPtr = &d;
    iPtr = &i;

    printf("dPtr = %p\n", dPtr);
    printf("iPtr = %p\n", iPtr);
    
    //rValue of dPtr
    //is equal to the lValue of d
    if(dPtr == &d){
        printf("dPtr == &d\n");
    }    

    return 0;

}

Note that C does not initialize pointers when we define them. We must explicitly assign the pointer variable the memory address of another variable of the same type. To print the value stored by the variable the pointer is pointer to, we use the dereferencing operator, *.

#include <stdio.h>

int main(void){

    char c = 's';

    char *cPtr = &c;

    printf("c = %c\n", c);
    printf("&c = %p\n", &c);
    printf("cPtr = %p\n", cPtr);
    printf("*cPtr = %c\n", *cPtr);    

    return 0;

}

A pointer must be of the same type as the variable its pointing to,  but, there is an exception to this rule. A pointer variable of type void can be used to point to variables of different types, however, we must cast it to the correct data type when we dereference it.

#include <stdio.h>

int main(void){

    int i = 404;
    unsigned uInt = 404405;
    double dare = 169.254;

    void * vPtr = &i;
    printf("%d\t", *(int *)vPtr);
    
    vPtr = &uInt;
    printf("%u\t", *(unsigned *)vPtr);

    vPtr = &dare;
    printf("%f\n", *(double *)vPtr);


    return 0;

}

To pass a variable to a function by reference instead of by value, place an & in front of the variable in the argument list, and an * everywhere it appears in the function body.

#include <stdio.h>

void incrementValue(int *i);

int main(void){

    int i = 46;
    int j = 73;

    printf("i = %d\t", i);

    incrementValue(&i);

    printf("now i = %d\n", i);

    printf("i = %d, j = %d", i, j);    

    return 0;

}


void incrementValue(int *i){
    *i+=2;
}

Our next program will swap the values of two integers using a function that accepts its arguments by reference.

#include <stdio.h>

void swapInts(int *a, int *b);

int main(void){

    int i = 42;
    int j = 73;

    printf("i = %d, j = %d\n", i, j);

    swapInts(&i, &j);


    printf("i = %d, j = %d\n", i, j);

    return 0;

}


void swapInts(int *a, int *b){
    int swap = *a;
    *a = *b;
    *b = swap;
}

Finally, it is possible to construct an array of pointers, with each pointer being a pointer to a specific data type.

#include <stdio.h>

int main(void){


    int i, j, k, l, m;

    int *intPtrs[5];

    intPtrs[0] = &i;
    intPtrs[1] = &j;
    intPtrs[2] = &k;
    intPtrs[3] = &l;
    intPtrs[4] = &m;

    for(int counter = 0; counter < 5; counter++){
        *intPtrs[counter] = (counter + 1) *3;
    }

    printf("%d \n", k);
    printf("%d \n", m);

    return 0;

}

If you can, please take a look at my Amazon.com author page: http://www.amazon.com/Al-Jensen/e/B008MN382O/

 

 

 

 

Dynamic Memory and Pointers in C

One of the blessings (some would say curse) of C is the degree to which we are able to micromanage dynamic memory in C.  By allocating and deallocating memory dynamically our programs can execute with greater flexibility and efficiency. Liked lists and queues, for instance, would be impossible to implement without dynamic memory allocation. The use of pointers is integral to tracking dynamically allocated memory in C.

Dynamic memory allocation in C begins with the use of malloc() or a similar function to allocate memory. This memory is then used by the program via a pointer. Finally, we deallocate the memory use the free() function.

#include <stdio.h>
#include <stdlib.h>

int main(void){
    
    double *d = (double*)malloc(sizeof(double));
    int *i = (int*)malloc(sizeof(int));
    
    *i = 2600;
    *d = 5.11;

    printf("%d\n", *i);
    printf("%f\n", *d);

    return 0;
    
}

The malloc() function accepts an argument that specifies the number of bytes to allocate. The malloc() function then returns a pointer to the memory allocated on the heap, unless it fails, in which case it returns a null pointer.

It’s important to be mindful of when we should and shouldn’t use the dereference operator with pointers to dynamic memory.

#include <stdio.h>
#include <stdlib.h>

int main(void){
    
    long long *longlong;
    char *c;
    
    
    longlong = (long long*) malloc(sizeof(long long));
    c = (char*) malloc(sizeof(char));
    
    *longlong = 56007800;
    *c = 'd';
    
    printf("%lld\n", *longlong);
    printf("%c\n", *c);
    
    return 0;
   

Finally, we must always remember to use the free() function to deallocate the memory when it is no longer needed. Unlike memory allcoated on the stack, we must explicitly free up the memory when we are done with it. Every time the malloc() function is called, we must at some point issue a corresponding call to the free() function.

#include <stdio.h>
#include <stdlib.h>

int main(void){
    
    int *a;
    double *b;
    char *c = (char *) malloc(sizeof(char));
    
    b = (double *) malloc(sizeof(double));
    a = (int *) malloc(sizeof (int));
    
    
    *a = 845;
    *b = 846.847;
    *c = 'd';
    
    printf("%d\t%f\t%c", *a, *b, *c);
    
    //important!!!
    free(a);
    free(b);
    free(c);
    
    return 0;
    
}

If we do not remember to use the free() function, we can have memory leaks. A memory leak happens with allocated memory is never used again but is not free.  This represents, at the very least, an inefficient use of system memory, and at worst can cause the program to crash. Memory leaks can also occur if we assign the pointer to a new memory location without freeing the memory it was previously pointing to.

#include <stdio.h>
#include <stdlib.h>


int main(void){
    
    int *i = (int*) malloc(sizeof(int));
    
    *i = 42;
    
    printf("%d", *i);
    printf("\t(%p)\n", i);
    
    //whoops!
    i = (int*) malloc(sizeof(int));
    
    printf("%d", *i);
    printf("\t(%p)\n", i);
    
    free(i);
    
    return 0;
    
}

In our final program, we will allocate memory to store a string. We will use then use the strcpy() function to store a string of characters in the allocated memory space. We can then output the string to the console using printf() with the %s conversion specifier.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(void){
    
    char *s = (char *) malloc(sizeof(char)*15);
    
    //note strcpy() function
    //is in string.h header file
    strcpy(s, "Saluton Mundo!");
    
    printf("%s\n", s);
    
    free(s);
    
    return 0;
    
}

I have a couple Kindle books for sale at Amazon; they’re readable both on a Kindle as well as by using the Kindle app on tablet or computer: http://www.amazon.com/Al-Jensen/e/B008MN382O/

 

 

 

 

 

 

 

Pointers, Constants, and Multiple Indirection in C

Pointers can use different levels of indirection; in fact, we often have pointer variables that are pointing to another pointer.

#include <stdio.h>

int main(void){
    
    char *bands[] = {"The Talking Heads", "Elvis Costello", "The Pixies", "Prince"};
    char *albums[] = {"Remain in Light", "This Year's Model", "Doolittle", "1999"};
    
    char **bandsAlbums[2];
    
    bandsAlbums[0] = bands;
    bandsAlbums[1] = albums; 
    
    //loops through first array
    for(int i = 0; i < 4; i++){
        printf("%s\t", *(*bandsAlbums+i));
    }
    
    //first element of second array
    //Remain In Light
    printf("\n%s\t", **(bandsAlbums+1));
    
    //second element of second array
    //This Year's Model
    printf("%s\t", *(*(bandsAlbums+1)+1));
    
    return 0;
    
}


Multiple indirection is most commonly used with strings, since a string is itself an array. However, we can use multiple indirection with any sort of array.

#include <stdio.h>

int main(void){
    
    int values[10];
    int tenValues[10];
    
    for(int i = 0; i < 10; i++){
        values[i] = i+1;
    }
    
    for(int i = 0; i < 10; i++){
        tenValues[i] = (i+1)*10;
    }
    
    int *iPtr[2];
    
    iPtr[0] = values;
    iPtr[1] = tenValues;
    
    //prints 1
    printf("%d\t", **iPtr);
    //prints 10
    printf("%d\t", **(iPtr+1));
    
    //prints 2
    printf("%d\t", *(*iPtr+1));
    //printfs 20
    printf("%d\t", *(*(iPtr+1)+1));
    
    return 0;
    
}

Note that there is not an inherent limit on the number of levels of indirection possible.

Pointers and the const keyword go hand-in-hand in C. A pointer can be defined as a pointer to a constant, which means that the pointer cannot be used to modify the value it is referencing.

#include <stdio.h>

int main(void){
    
    double d01 = 22.41;
    double d02 = 169.254;
    
    double *dPtr;
    const double *const_dPtr;
    
    dPtr = &d01;
    const_dPtr = &d02;
    
    printf("%f\t", *dPtr);
    //can modify value via 
    //regular pointer
    (*dPtr)++;
    printf("%f\t", *dPtr);
            
    printf("\n%f\t", *const_dPtr);
    //we cannot modify value via
    //const pointer
    d02++;
    printf("%f\n", *const_dPtr);
            
    return 0;
    
}

We can dereference a constant pointer, but we cannot change the value to which it is pointing. We can, however, change what the value the pointer is pointing at; the pointer value itself is not constant. The pointer can be changed to point to a different value.

#include <stdio.h>

int main(void){
    

    int a = 42;
    int b = 47;
    
    const int *const_ptr = &a;
    
    
    printf("%d\n", *const_ptr);
    
    const_ptr = &b;
    
    printf("%d\n", *const_ptr);
    
    return 0;
    
}

We can declare a constant pointer to a nonconstant. To do so , we switch the data type and the const keyword. When we do this, it is possible to change the value stored in the variable to which the pointer is pointing; however, we cannot direct the pointer to point to a different variable.

#include <stdio.h>

int main(void){
    
    char a = 'a';
    char b = 'b';
    
    char *const const_ptr1 = &a;
    char *const const_ptr2 = &b;
    
    *const_ptr1+=2;
    *const_ptr2+=2;
    
    //prints c and d, respectively
    printf("%c\n", *const_ptr1);
    printf("%c\n", *const_ptr2);
    
    return 0;
    
    
}

If you can, please purchase a copy of my book. It can be read on your Amazon Kindle, or on an Android tablet or iPad using the Amazon app: http://www.amazon.com/Big-Als-C-Standard-ebook/dp/B00A4JGE0M/