/* Line 1 */ #include < stdio.h >
/* Line 2 */
/* Line 3 */ main()
/* Line 4 */ {
/* Line 5 */ int i, array[10];
/* Line 6 */ int *ip, *a1;
/* Line 7 */ int **ipp;
/* Line 8 */
/* Line 9 */ ip = &i;
/* Line 10 */ ipp = &ip;
/* Line 11 */ a1 = &(array[1]);
/* Line 12 */
/* Line 13 */ for (i = 0; i < 10; i++) array[i] = i;
/* Line 14 */
/* Line 15 */ i = 11;
/* Line 16 */
/* Line 17 */ printf("ip: 0x%x, &ip: 0x%x, array: 0x%x\n", ip, &ip, array);
/* Line 18 */ printf("\n");
/* Line 19 */
/* Line 20 */
/* Line 21 */ printf("&i: 0x%x\n", &i);
/* Line 22 */ printf("ipp: 0x%x, *ipp: 0x%x, **ipp: 0x%x\n", ipp, *ipp, **ipp);
/* Line 23 */ printf("\n");
/* Line 24 */ printf("a1: 0x%x, *a1: 0x%x\n", a1, *a1);
/* Line 25 */
/* Line 26 */ a1 += 4;
/* Line 27 */ *a1 = 500;
/* Line 28 */
/* Line 29 */ for (i = 0; i < 10; i++) {
/* Line 30 */ printf("%d ", array[i]);
/* Line 31 */ }
/* Line 32 */ printf("\n");
/* Line 33 */ }
When you run this, the first line of output is:
UNIX> quiz ip: 0xeffff9fc, &ip: 0xeffff9cc, array: 0xeffff9d0What is the rest of the output?
This is tricky, but you should be able to do it with all you currently know about pointers. This is the kind of question I am fond of asking on tests. Here's the answer. If you want to make sure you're doing things right, try to draw a picture of memory and fill in what that first line tells you. Here would be my picture. We'll start with a blank slate with the relevant addresses from the first line of the program:
0xeffff9cc: | | 0xeffff9d0: | | 0xeffff9d4: | | 0xeffff9d8: | | 0xeffff9dc: | | 0xeffff9e0: | | 0xeffff9e4: | | 0xeffff9e8: | | 0xeffff9ec: | | 0xeffff9f0: | | 0xeffff9f4: | | 0xeffff9f8: | | 0xeffff9fc: | | |
Now, what do we know from the first line of output. Well, the address of ip is 0xeffff9cc, and its value is 0xeffff9fc. So we can draw in its value at that address:
0xeffff9cc: | ip = 0xeffff9fc | 0xeffff9d0: | | 0xeffff9d4: | | 0xeffff9d8: | | 0xeffff9dc: | | 0xeffff9e0: | | 0xeffff9e4: | | 0xeffff9e8: | | 0xeffff9ec: | | 0xeffff9f0: | | 0xeffff9f4: | | 0xeffff9f8: | | 0xeffff9fc: | | |
From line 9, we know that the address of i is equal to ip. Moreover, i's value is 11, so we can draw that in:
0xeffff9cc: | ip = 0xeffff9fc | 0xeffff9d0: | | 0xeffff9d4: | | 0xeffff9d8: | | 0xeffff9dc: | | 0xeffff9e0: | | 0xeffff9e4: | | 0xeffff9e8: | | 0xeffff9ec: | | 0xeffff9f0: | | 0xeffff9f4: | | 0xeffff9f8: | | 0xeffff9fc: | i = 11 | |
Now, array is a pointer to the first element of the 10-element array. Since its value is 0xeffff9d0, we can draw in all ten elements of the array:
0xeffff9cc: | ip = 0xeffff9fc | 0xeffff9d0: | array[0] = 0 | 0xeffff9d4: | array[1] = 1 | 0xeffff9d8: | array[2] = 2 | 0xeffff9dc: | array[3] = 3 | 0xeffff9e0: | array[4] = 4 | 0xeffff9e4: | array[5] = 5 | 0xeffff9e8: | array[6] = 6 | 0xeffff9ec: | array[7] = 7 | 0xeffff9f0: | array[8] = 8 | 0xeffff9f4: | array[9] = 9 | 0xeffff9f8: | | 0xeffff9fc: | i = 11 | |
Now we know all we need to know. Since &i equals ip, the first line of output is "&i: 0xeffff9fc." Next, from line 10 of the program, we know that ipp equals the address of ip. So the next line is:
ipp: 0xeffff9cc, *ipp: 0xeffff9fc, **ipp: 0xbNote, that last word is 0xb, and not 11, because we are printing 11 in hexadecimal.
Now, since a1 is a pointer to array[1], its value is 0xeffff9d4. Thus, our next line of output (after the blank line) is:
a1: 0xeffff9d4, *a1: 0x1Finally, the statement ``a1 += 4'' is what's known as pointer arithmetic. It sets a1 ahead four ints. Therefore, it adds 16 to the value of a1 -- 16 because ints are 4 bytes: 4*4 = 16. After the statement it points to array[5]. Therefore, the last line is
0 1 2 3 4 500 6 7 8 9Here is the entire output:
ip: 0xeffff9fc, &ip: 0xeffff9cc, array: 0xeffff9d0 &i: 0xeffff9fc ipp: 0xeffff9cc, *ipp: 0xeffff9fc, **ipp: 0xb a1: 0xeffff9d4, *a1: 0x1 0 1 2 3 4 500 6 7 8 9 |
char *strcpy(char *s1, char *s2);Strcpy() assumes that s2 is a null-terminated string, and that s1 is a (char *) with enough characters to hold s2, including the null character at the end. Strcpy() then copies s2 to s1. It also returns s1. Why would you return your first argument? We'll get to that when we show an easy way to implement strdup().
Here's a simple program that uses strcpy() to initialize three strings and print them out (this is in strcpy.c):
#include <stdio.h>
#include <string.h>
main()
{
char give[5];
char him[5];
char six[5];
strcpy(give, "Give");
strcpy(him, "Him");
strcpy(six, "Six!");
printf("%s %s %s\n", give, him, six);
}
|
It runs fine:
UNIX> strcpy Give Him Six! UNIX>Suppose I try to copy a string that's too big. For example, look at strcpy2.c:
#include <stdio.h>
#include <string.h>
main()
{
char give[5];
char him[5];
char six[5];
printf("give: 0x%x him: 0x%x six: 0x%x\n", give, him, six);
strcpy(give, "Give");
strcpy(him, "Him");
strcpy(six, "Six!");
printf("%s %s %s\n", give, him, six);
strcpy(him, "T.J. Houshmandzadeh");
printf("%s %s %s\n", give, him, six);
}
|
Now run it:
UNIX> strcpy2 give: 0xbfffe060 him: 0xbfffe050 six: 0xbfffe040 Give Him Six! deh T.J. Houshmandzadeh Six! UNIX>Take a minute and try to figure out what's going on. Look at the following picture of memory. When we start, space has been allocated for give, him and six:
|----4 bytes----|
| |
six----------> | | 0xbfffe040
| | 0xbfffe044
| | 0xbfffe048
| | 0xbfffe04c
him----------> | | 0xbfffe050
| | 0xbfffe054
| | 0xbfffe058
| | 0xbfffe05c
give---------> | | 0xbfffe060
| | 0xbfffe064
| | 0xbfffe068
| | 0xbfffe06c
Now, we make the first three strcpy() calls. At the point of
the first printf() statement,
memory looks like:
six----------> |'S'|'i'|'x'|'!'| 0xbfffe040
| 0 | | | | 0xbfffe044
| | | | | 0xbfffe048
| | | | | 0xbfffe04c
him----------> |'H'|'i'|'m'| 0 | 0xbfffe050
| | | | | 0xbfffe054
| | | | | 0xbfffe058
| | | | | 0xbfffe05c
give---------> |'G'|'i'|'v'|'e'| 0xbfffe060
| 0 | | | | 0xbfffe064
| | 0xbfffe068
| | 0xbfffe06c
Now, we make the call strcpy(him, "T.J. Houshmandzadeh"). What happens is
that the entire string is copied to him, and this overruns the memory
allocated for give:
six----------> |'S'|'i'|'x'|'!'| 0xbfffe040
| 0 | | | | 0xbfffe044
| | | | | 0xbfffe048
| | | | | 0xbfffe04c
him----------> |'T'|'.'|'J'|'.'| 0xbfffe050
|' '|'H'|'o'|'u'| 0xbfffe054
|'s'|'h'|'m'|'a'| 0xbfffe058
|'n'|'d'|'z'|'a'| 0xbfffe05c
give---------> |'d'|'e'|'h'| 0 | 0xbfffe060
| 0 | | | | 0xbfffe064
| | 0xbfffe068
| | 0xbfffe06c
So this means that him is indeed "T.J. Houshmandzadeh", but
give has been modified as well, to be "deh". This
accounts for the printout of:
deh T.J. Houshmandzadeh Six!The bottom line is that when you modify memory that you have not allocated (as I did when I called strcpy(him, "T.J. Houshmandzadeh");), then strange things will happen. They have explanations, but until you figure it out, it will be confusing. If you're lucky, you get a segmentation violation or a bus error. If you're unlucky, you get wierd, inexplicable output. A corollary of this is that when you get a segmentation violation, a bus error, or wierd, inexplicable output, then chances are you have modified memory that you didn't allocate.
char *strcat(char *s1, char *s2);Strcat() assumes that s1 and s2 are both null-terminated strings. Strcat() then concatenates s2 to the end of s1. I don't know what it returns -- read the man page if you care. Strcat() assumes that there is enough space in s1 to hold these extra characters. Otherwise, you'll start stomping over memory that you didn't allocate. Here is a simple example: (this is in strcat.c):
#include <stdio.h>
#include <string.h>
main()
{
char givehimsix[15];
strcpy(givehimsix, "Give");
printf("%s\n", givehimsix);
strcat(givehimsix, " Him");
printf("%s\n", givehimsix);
strcat(givehimsix, " Six!");
printf("%s\n", givehimsix);
}
|
The output is predictable:
UNIX> strcat Give Give Him Give Him Six! UNIX>Look at strcat2.c. Can you explain why the output is the way that it is? Try filling memory as in the strcpy2 example above.
UNIX> strcat2 give: 0xbfffe060 him: 0xbfffe050 six: 0xbfffe040 Give Him Six! deh T.J. Houshmandzadeh Six! deh Help! T.J. Houshmandzadeh Help! Six! UNIX>
int strlen(char *s);Strlen() assumes that s is a null-terminated string. It returns the number of characters before the null character. Strlen() is pretty obvious: (this is in strlen.c):
#include <stdio.h>
#include <string.h>
main()
{
char give[5];
char him[5];
char six[5];
strcpy(give, "Give");
strcpy(him, "Him");
strcpy(six, "Six!");
printf("%s %s %s\n", give, him, six);
printf("%d %d %d\n", strlen(give), strlen(him), strlen(six));
}
|
Output:
UNIX> strlen Give Him Six! 4 3 4
char *strchr(char *s, int c);Strchr() assumes that s is a null-terminated string. C is an integer, but it is treated as a character. Strchr() returns a pointer to the first occurrence of the character equal to c in s. If s does not contain c, then it returns NULL.
Here is a simple program that prints out whether each line of standard input contains a space (this is in strchr.c):
#include <stdio.h>
#include <string.h>
main()
{
char line[100];
char *ptr;
while (fgets(line, 100, stdin) != NULL) {
ptr = strchr(line, ' ');
if (ptr == NULL) {
printf("No spaces\n");
} else {
printf("Space at character %d\n", ptr-line);
}
}
}
|
Note, I'm doing a little pointer arithmetic here -- ptr-line returns the number of characters between line and ptr. Here's an example of this running:
UNIX> strchr Jim No spaces Jim Plank Space at character 3 Jamal Lewis Space at character 5 HI! Space at character 0 HI! Space at character 0 UNIX>We can modify this to print out where all the spaces are. Check out strchr2.c:
UNIX> strchr2 Jim No spaces Jim Plank Space at character 3 Jim Plank Space at character 3 Space at character 4 Give Him Six!!! Space at character 0 Space at character 1 Space at character 2 Space at character 7 Space at character 8 Space at character 9 Space at character 13 Space at character 14 Space at character 15 UNIX>Go over the code -- why do I say
ptr = strchr(ptr+1, ' ');
instead of
ptr = strchr(ptr, ' ');
If you don't know, copy the code, modify it, and see for yourself!
#include <stdio.h>
main()
{
char line[1000];
char *last10[10];
int nlines, i;
nlines = 0;
while (fgets(line, 1000, stdin) != NULL) {
last10[nlines%10] = line;
nlines++;
}
if (nlines <= 10) {
for (i = 0; i < nlines; i++) printf("%s", last10[i]);
} else {
for (i = nlines-10; i < nlines; i++) {
printf("0x%x %s", last10[i%10], last10[i%10]);
}
}
printf("Line is 0x%x\n", line);
}
|
Go over this until you understand what's going on. Unfortunately, it won't work right:
UNIX> cat tailinput Line 1 Line 2 Line 3 Line 4 Line 5 Line 6 Line 7 Line 8 Line 9 Line 10 Line 11 UNIX> tail10a < tailinput Line 11 Line 11 Line 11 Line 11 Line 11 Line 11 Line 11 Line 11 Line 11 Line 11 UNIX>Why? Because you're setting each entry of last10 to be line, which is getting overwritten at each fgets() call.
One easy way to fix this is to make last10 an array of 10 arrays of 1000 characters each, and then to use strcpy(). This is in tail10b.c. It works correctly:
UNIX> tail10b < tailinput
Line 2
Line 3
Line 4
Line 5
Line 6
Line 7
Line 8
Line 9
Line 10
Line 11
UNIX> tail10b < tail10b.c
}
if (nlines <= 10) {
for (i = 0; i < nlines; i++) puts(last10[i]);
} else {
for (i = nlines-10; i < nlines; i++) {
puts(last10[i%10]);
}
}
}
UNIX>
So we'll use malloc(). This is C's version of new. Malloc(n) returns a pointer to n bytes of memory, given to you by the operating system. We say that this memory comes from the ``heap.'' Once you get this memory, you can use it anywhere -- you can pass it to and from procedure calls without worrying about it going away like you do local memory.
Below, we show tailany1.c, which uses malloc() to allocate the array of 1000 character arrays. Note how the code basically looks like tail10b.c:
#include <stdio.h>
#include <string.h>
main(int argc, char **argv)
{
char line[1000];
char **lastn;
int nlines, i, n;
/* Error check the input */
if (argc != 2) {
fprintf(stderr, "usage: tailany1 n\n");
exit(1);
}
n = atoi(argv[1]);
if (n <= 0) exit(0);
/* Allocate the array */
lastn = (char **) malloc(sizeof(char *)*n);
if (lastn == NULL) { perror("malloc"); exit(1); }
for (i = 0; i < n; i++) {
lastn[i] = (char *) malloc(sizeof(char)*1000);
if (lastn[i] == NULL) { perror("malloc"); exit(1); }
}
/* Read the input */
nlines = 0;
while (fgets(line, 1000, stdin) != NULL) {
strcpy(lastn[nlines%n], line);
nlines++;
}
/* Print the last n lines */
if (nlines <= n) {
for (i = 0; i < nlines; i++) printf("%s", lastn[i]);
} else {
for (i = nlines-n; i < nlines; i++) {
printf("%s", lastn[i%n]);
}
}
}
|
Note that when we call malloc(), we cast its return value to be the type we want. This is to keep the compiler happy -- malloc() returns a pointer -- we make the type cast statement to tell the compiler that we know what kind of pointer we want.
This works just like we think it should:
UNIX> tailany1 < tailinput usage: tailany1 n UNIX> tailany1 1 < tailinput Line 11 UNIX> tailany1 2 < tailinput Line 10 Line 11 UNIX> tailany1 50 < tailinput Line 1 Line 2 Line 3 Line 4 Line 5 Line 6 Line 7 Line 8 Line 9 Line 10 Line 11 UNIX> tailany1 100000 < tailinput Line 1 Line 2 Line 3 Line 4 Line 5 Line 6 Line 7 Line 8 Line 9 Line 10 Line 11 UNIX>You'll note that if you run tailany1 100000 < tailinput, it will take a little while. This is the time that it takes doing all those mallocs(). How many bytes is it allocating? 100000*4 for lastn, and 1000 for each entry of lastn. That makes 400000+100000*1000 = 100400000. That's roughly 100 megabytes, which seems kind of wasteful. It is. Try it with a bigger number, and see if you can get tailany1 to break!
Here's one solution. Instead of allocating arrays of 1000 characters, let's go back to using (char *)'s instead. Then after reading line, well call strdup() instead of strcpy().
Strdup(s) basically does the following:
char *strdup(char *s)
{
char *news;
news = (char *) malloc((strlen(s)+1)*sizeof(char));
strcpy(news, s);
return news;
}
In other words, it gives you a new copy of s that it has
malloc()'d for you.
Tailany2.c uses strdup() -- check it out and make sure you understand how it works. Now you'll see that tailany2 100000 < tailinput runs much faster.
There's one last problem with tailany2. That is that if nlines is much larger than n, then you are wasting a lot of memory because you are calling strdup() to copy lines, but you never give the memory back to the operating system. So your heap just grows and grows, when it doesn't have to. You can fix this with free(), which is just like C++'s delete. Free's syntax is:
void free(void *ptr);You can free() anything that you have allocated with malloc() (or strdup(), since strdup() calls malloc()). tailany3.c fixes the problem by calling free() whenever you are about to overwrite a pointer that was allocated with strdup(). Check it out.