Assignment #6 Answers

Introductory C Programming

UW Experimental College

Assignment #6 ANSWERS


Question 1. If we say int i = 5; int *ip = &i; then what is ip? What is its value?

ip is a variable which can point to an int (that is, its value will be a pointer to an int; or informally, we say that ip is ``a pointer to an int''). Its value is a pointer which points to the variable i.

Question 2. If ip is a pointer to an int, what does ip++ mean? What does *ip++ = 0 do?

ip++ means about the same as it does for any other variable: increment it by 1, that is, as if we had written ip = ip + 1. In the case of a pointer, this means to make it point to the object (the int) one past the one it used to. *ip++ = 0 sets the int variable pointed to by ip to 0, and then increments ip to point to the next int.

Question 3. How much memory does the call malloc(10) allocate? What if you want enough memory for 10 ints?

malloc(10) allocates 10 bytes, which is enough space for 10 chars. To allocate space for 10 ints, you could call malloc(10 * sizeof(int)) .

Question 4. If char and int pointers are different, how is it possible to write

	char *cp = malloc(10);
	int *ip = malloc(sizeof(int));
without error on either line?

malloc is declared as returning the special, ``generic'' pointer type void *, which can be (and is) automatically converted to different pointer types, as needed.

Exercise 1. Write a program to read lines and print only those containing a certain word.

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

extern int getline(char [], int);

int main()
{
	char line[100];
	char *pat = "hello";

	while(getline(line, 100) != EOF)
		{
		if(strstr(line, pat) != NULL)
			printf("%s\n", line);
		}

	return 0;
}

Exercise 2. Rewrite the checkbook-balancing program to use the getwords function to make it easy to take the word ``check'' or ``deposit'', and the amount, from a single line.

#include <stdio.h>
#include <stdlib.h>	/* for atof() */

#define MAXLINE 100
#define MAXWORDS 10

extern int getline(char [], int);
extern int getwords(char *, char *[], int);

int main()
{
	double balance = 0.0;
	char line[MAXLINE];
	char *words[MAXWORDS];
	int nwords;

	while (getline(line, MAXLINE) > 0)
		{
		nwords = getwords(line, words, MAXWORDS);

		if(nwords == 0)	/* blank line */
			continue;

		if(strcmp(words[0], "deposit") == 0)
			{
			if(nwords < 2)
				{
				printf("missing amount\n");
				continue;
				}
			balance += atof(words[1]);
			}
		else if(strcmp(words[0], "check") == 0)
			{
			if(nwords < 2)
				{
				printf("missing amount\n");
				continue;
				}
			balance -= atof(words[1]);
			}
		else	{
			printf("bad data line: \"%s\"\n", words[0]);
			continue;
			}

		printf("balance: %.2f\n", balance);
		}
	
	return 0;
}

Exercise 3. Rewrite the line-reversing function to use pointers.

Here is one way:

int reverse(char *string)
{
	char *lp = string;			/* left pointer */
	char *rp = &string[strlen(string)-1];	/* right pointer */
	char tmp;
	while(lp < rp)
		{
		tmp = *lp;
		*lp = *rp;
		*rp = tmp;
		lp++;
		rp--;
		}
	return 0;
}

Exercise 4. Rewrite the character-counting function to use pointers.

int countnchars(char *string, int ch)
{
	char *p;
	int count = 0;
	for(p = string; *p != '\0'; p++)
		{
		if(*p == ch)
			count++;
		}
	return count;
}

Exercise 5. Rewrite the string concatenation program to call malloc to allocate memory for the concatenated result.

#include <stdio.h>
#include <stdlib.h>	/* for malloc */
#include <string.h>	/* for strcpy and strcat */

#define MAXLINE 100

extern int getline(char [], int);

int main()
{
	char string1[MAXLINE], string2[MAXLINE];
	int len1, len2;
	char *newstring;

	printf("enter first string:\n");
	len1 = getline(string1, 100);
	printf("enter second string:\n");
	len2 = getline(string2, 100);

	if(len1 == EOF || len2 == EOF)
		exit(1);

	newstring = malloc(len1 + len2 + 1);	/* +1 for \0 */

	if(newstring == NULL)
		{
		printf("out of memory\n");
		exit(1);
		}

	strcpy(newstring, string1);
	strcat(newstring, string2);

	printf("%s\n", newstring);

	return 0;
}

Exercise 6. Rewrite the string-replacing function to use pointers.

Here is one way:

void replace(char string[], char *from, char *to)
{
	char *start, *p1, *p2;
	for(start = string; *start != '\0'; start++)
		{
		p1 = from;
		p2 = start;
		while(*p1 != '\0')
			{
			if(*p1 != *p2)
				break;
			p1++;
			p2++;
			}
		if(*p1 == '\0')
			{
			for(p1 = to; *p1 != '\0'; p1++)
				*start++ = *p1;
			return;
			}
		}
}
The bulk of this code is a copy of the mystrstr function in the notes, chapter 10, section 10.4, p. 8. (Since strstr's job is to find one string within another, it's a natural for the first half of replace.) We could also call strstr directly, simplifying replace:
#include <string.h>

void replace(char string[], char *from, char *to)
{
	char *p1;
	char *start = strstr(string, from);
	if(start != NULL)
		{
		for(p1 = to; *p1 != '\0'; p1++)
			*start++ = *p1;
		return;
		}
}
Again, we might wonder about the case when the string to be edited contains multiple occurrences of the from string, and ask whether replace should replace the first one, or all of them. The problem statement didn't make this detail clear. Our first two implementations have replaced only the first occurrence. It happens, though, that it's trivial to rewrite our first version to make it replace all occurrences--just omit the return after the first string has been replaced:
void replace(char string[], char *from, char *to)
{
	char *start, *p1, *p2;
	for(start = string; *start != '\0'; start++)
		{
		p1 = from;
		p2 = start;
		while(*p1 != '\0')
			{
			if(*p1 != *p2)
				break;
			p1++;
			p2++;
			}
		if(*p1 == '\0')
			{
			for(p1 = to; *p1 != '\0'; p1++)
				*start++ = *p1;
			}
		}
}
Rewriting the second version wouldn't be much harder.

Exercise 7. Write a program to read lines of text up to EOF, and then print them out in reverse order.

One of the reasons this program is harder is that it uses pointers to pointers. When we used pointers to simulate an array of integers, we used pointers to integers. In this program, we want to simulate an array of strings, or an array of pointers to characters. Therefore, we use pointers to pointers to characters, or char **.

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

extern int getline(char [], int);

#define MAXLINE 100

int main()
{
int i;
char line[MAXLINE];
char **lines;
int nalloc, nitems;

nalloc = 10;
lines = malloc(nalloc * sizeof(char *));
if(lines == NULL)
	{
	printf("out of memory\n");
	exit(1);
	}

nitems = 0;

while(getline(line, MAXLINE) != EOF)
	{
	if(nitems >= nalloc)
		{
		char **newp;
		nalloc += 10;
		newp = realloc(lines, nalloc * sizeof(char *));
		if(newp == NULL)
			{
			printf("out of memory\n");
			exit(1);
			}
		lines = newp;
		}

	lines[nitems] = malloc(strlen(line) + 1);
	strcpy(lines[nitems], line);
	nitems++;
	}

for(i = nitems - 1; i >= 0; i--)
	printf("%s\n", lines[i]);

return 0;
}
Notice that for each line we read, we call malloc to allocate space for a copy of it, and then use strcpy to make the copy. We could not simply set
	lines[nitems++] = line;
each time, because line is a single array, which can only hold one line, and it gets overwritten with the contents of each input line. (In other words, if we didn't call malloc and make a copy, we'd end up at the end with only the last line. You might try it to see what happens.)

Extra credit: remove the restriction imposed by the fixed-size array; allow the program to accept arbitrarily-long lines.

First we will write another version of getline, called mgetline. mgetline calls malloc and realloc to get enough memory for the line it's currently reading, regardless of how long that line is. mgetline returns a pointer to the allocated memory; therefore, the caller does not have to pass an array for mgetline to read into. (It will be the caller's responsibility to free the memory when it doesn't need the line of text any more.)

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

char *mgetline()
{
char *line;
int nalloc = 10;
int nch = 0;
int c;

line = malloc(nalloc + 1);
if(line == NULL)
	{
	printf("out of memory\n");
	exit(1);
	}

while((c = getchar()) != EOF)
	{
	if(c == '\n')
		break;

	if(nch >= nalloc)
		{
		char *newp;
		nalloc += 10;
		newp = realloc(line, nalloc + 1);
		if(newp == NULL)
			{
			printf("out of memory\n");
			exit(1);
			}
		line = newp;
		}
	line[nch++] = c;
	}

if(c == EOF && nch == 0)
	{
	free(line);
	return NULL;
	}

line[nch] = '\0';

return line;
}
Now we can rewrite the line-reversing program to call mgetline. Note that we no longer need the local line array. Instead, we use a pointer, linep, which holds the return value from mgetline. Since mgetline returns a new pointer to a new block of memory each time we call it, in this program (unlike the previous one) we can set
	lines[nitems++] = linep;
without overwriting anything.
#include <stdio.h>
#include <stdlib.h>

extern char *mgetline();

int main()
{
int i;
char *linep;
char **lines;
int nalloc, nitems;

nalloc = 10;
lines = malloc(nalloc * sizeof(char *));
if(lines == NULL)
	{
	printf("out of memory\n");
	exit(1);
	}

nitems = 0;

while((linep = mgetline()) != NULL)
	{
	if(nitems >= nalloc)
		{
		char **newp;
		nalloc += 10;
		newp = realloc(lines, nalloc * sizeof(char *));
		if(newp == NULL)
			{
			printf("out of memory\n");
			exit(1);
			}
		lines = newp;
		}

	lines[nitems++] = linep;
	}

for(i = nitems - 1; i >= 0; i--)
	printf("%s\n", lines[i]);

return 0;
}


This page by Steve Summit // Copyright 1995-9 // mail feedback