CS 2213 Advanced Programming
Arrays and Pointers


Read Chapter 5 of the text.
Previous Topic: Functions and Program Structure in C
Next Topic: Structures
Start of Class Thursday, February 8, 2001
Start of Class Tuesday, February 13, 2001
Start of Class Thursday, February 15, 2001
Start of Class Tuesday, February 20, 2001
Start of Class Tuesday, February 27, 2001
Start of Class Thursday, March 1, 2001
Start of Class Tuesday, March 6, 2001
Start of Class Thursday, March 8, 2001

Introduction

Program data is stored in memory. Consider the following program:
#include <stdio.h>

int main() {
int x = 5;
int y = 7;
int z = 25;

printf("The value of x is %2d and it is stored at address %p\n",x,&x);
printf("The value of y is %2d and it is stored at address %p\n",y,&y);
printf("The value of z is %2d and it is stored at address %p\n",z,&z);
return 0;
}
In produces the following output on one of our systems:
The value of x is  5 and it is stored at address ffbef1f8
The value of y is  7 and it is stored at address ffbef1f4
The value of z is 25 and it is stored at address ffbef1f0
The results may be different on different systems and you may get different values each time you run the program.

On our system, addresses are represented in hexadecimal (base 16) and you will notice that the addresses differ by 4.

Also notice that z is stored first.

This may be unexpected, but remember that you cannot assume anything about what addresses will be used for variables.

Arrays Arrays are stored in consecutive memory cells.

Consider the following program:
#include <stdio.h>

int main() {
   char a[10];
   int i;
   for (i=0;i<10;i++)
      a[i] = 'A' + i;

   printf("The address of the array is %p\n",a);
   printf("The value stored at index 5 is %c and its address is %p\n",
            a[5],&a[5]);
   return 0;
}
The address of the array is ffbef1f2
The value stored at index 5 is F and its address is ffbef1f7
Notice that the address of a[5] is 5 more than the address of a
Note that this program works because [] has higher precedence than &
Suppose we change it to an array of integers:
#include <stdio.h>

int main() {
   int x[10];
   int i;
   for (i=0;i<10;i++)
      x[i] = i;

   printf("The address of the array is %p\n",x);
   printf("The value stored at index 5 is %c and its address is %p\n",
           x[5],&x[5]);
   return 0;
}
We get the following:
The address of the array is ffbef1d4
The value stored at index 5 is  and its address is ffbef1e8
In base 16, e8 is 20 more than d4
This shows that each integer takes up 4 bytes.


5.1 Pointers and Addresses

The * operator is called the indirection or dereferencing operator.
It is used to indicate the value that a pointer is referencing.

Example:

#include <stdio.h>

int main() {
   int x = 1;
   int y;
   int z[10];
   int *ip;

   z[0] = 100;
   z[1] = 101;
   z[2] = 102;
   ip = &x;
   y = *ip;
   *ip = 0;
   ip = &z[0];
   *ip = 5;
   printf("z[0] now %d\n",z[0]);
   ++*ip;
   printf("z[0] now %d\n",z[0]);
   (*ip)++;
   printf("z[0] now %d\n",z[0]);
   printf("This thing is %d\n",*ip++);
   printf("ip now points to %d\n",*ip);
   return 0;
}
z[0] now 5
z[0] now 6
z[0] now 7
This thing is 7
ip now points to 101


5.2 Pointers and Function Arguments

addressvalue
1000100
1004101
1008102
1012103
1016104
1020105
1024106
1028107
1032108
1036109
1040110
1044111
1048112
1052113
1056114
Consider a program whose memory is shown at the left.
We assume 4 bytes per integer and that only integers are stored in memory. Here is a piece of the program.
int x;
x = 5;
printf("x = %d\n",x);
increment(x);
printf("x = %d\n",x);
...
void increment(int x) {
   x++;
}
What does the output look like?
Suppose that x is stored at location 1004 and the increment function uses location 1036 for its storage of its parameter.
What does the memory look like after the call?

Now consider the following:

int x;
x = 5;
printf("x = %d\n",x);
increment1(&x);
printf("x = %d\n",x);
...
void increment1(int *x) {
   (*x)++;
}
Now what is the output and what does the memory look like assuming increment1 uses location 1032 for its parameter?

Recall that an array name is a pointer to the start of the array.

Consider the following function:

void fillArray(int a[], int size) {
   int i;
   for (i=0;i<size;i++)
      a[i] = i*i;
}
This works because the address of the array is being passed to the function.
How much local memory does this function need?

Answer: space for one pointer (a) and two integers (size,i).
Consider the following calling program with the memory given above.

int main() {
   int a[5];
   int size = 5;
   fillArray(a,size);
}
Assume that a starts at location 1008 and that size is stored in location 1004.
The function fillArray uses location 1044 and 1048 for its two parameters and 1052 for i.
What does the memory look like before and after the function call?

The following does not work:

#include <stdio.h>

void swap(int x, int y) {
   int temp;
   temp = x;
   x = y;
   y = temp;
}

int main() {
   int a = 2;
   int b = 3;
   printf("a = %d and b = %d\n",a,b);
   swap(a,b);
   printf("a = %d and b = %d\n",a,b);
   return 0;
}
What is the output generated?

The following does work:

 
#include <stdio.h> 
 
void swap(int *px, int *py) {
   int temp; 
   temp = *px; 
   *px = *py; 
   *py = temp; 
} 
 
int main() { 
   int a = 2; 
   int b = 3; 
   printf("a = %d and b = %d\n",a,b); 
   swap(&a,&b); 
   printf("a = %d and b = %d\n",a,b);
   return 0; 
} 


5.3 Pointers and Arrays

As noted earlier, an array name is a pointer to the start of the array.

Consider the following:

   int a[10];
   int *pa;
   pa = a;
The last line could also have been written pa = &a[0];.

Similarities between the a and pa.

Differences between the a and pa.


About programming Assignment 0:
comments
addposint
subposint


Pointer Arithmetic

You can add an integer to a pointer to get another pointer.

pa + n means the same thing as &(pa[n]).

Note that pa+1 is not one more than pa unless pa points to a char.

Adding 1 to a pointer causes it to point to the next value.

You can do the same with a:
*(a+i) means the same thing as a[i].

Since pa is a variable, and you can add one to it, pa++ makes sense.

However, a is not a variable (it is a constant pointer) and so you cannot do a++.

When used as a formal parameter to a function, the declarations
char *a and char a[] are equivalent.
In the latter, a is not treated as a constant.

Here are two versions of strlen which are equivalent:

int strlen(char s[]) {
   int n;
   for (n=0; *s != '\0'; s++)
      n++;
   return n;
}

int strlen(char *s) {
   int n;
   for (n=0; *s != '\0'; s++)
      n++;
   return n;
}
Note that even in the first of these, s is not considered a constant.

If a pointer (or an array) is passed to either of these, the value in the calling function is not changed.

addressvalueaddressvalue
10001001016116
10011011017117
10021021018118
10031031019119
10041041020110
10051051021121
10061061022122
10071071023123
10081081024124
10091091025125
10101101026126
10111111027127
10121121028128
10131131029129
10141141030130
10151151031131
Consider a program whose memory is shown at the left.
We assume 4 bytes per integer or pointer.
Here is a piece of a program.
Assume that the main program stores the array a starting at location 1004 and that it uses 1000 to store i.
Assume that strlen stores its parameter at 1024 and n at 1028.
int strlen(char s[]) {
   int n;
   for (n=0; *s != '\0'; s++)
      n++;
   return n;
}

int main() {
   char a[4];
   int i;
   for (i=0;i<3;i++)
      a[i] = 'A'+i;
   a[3] = '\0';
   i = strlen(a)
   printf("The length is %d\n",i);
}
Tricky question: at what location is the value of a stored?


5.4 Address Arithmetic

This section of the text discusses how to write a memory allocator.

Instead we will look at the library routines for doing memory allocation.

First, here are some the the key ideas that are used in this section of the text.
Assume that p and q are pointer variables which have been initialized to point to elements of the same array.

Memory Allocation
Problem: Write a function that reads a line from standard input and returns a string containing that line.

This sounds a lot like the getline that we discussed earlier.
int getline(char line[], int maxline);

However, this function was given an array of a fixed size and stored the line in this array.

It could not handle the case of a line larger than the array.

We would like a function that will create and array of the appropriate size and return a pointer to it to the calling function.

What is wrong with the following?
(The problem is not just he maximum line size.)

#define MAXLINE 1000
char *getLine() {
   char a[MAXLINE];
   getline(a,MAXLINE);
   return a;
}


About the exam.

Exam seating:
Every other seat starting at the ends of each row
First few rows reserved for overflow


Warning: Programming Assignment 1 makefile
make clean
will remove heaptester.o
because it does:
rm -f heaptest heaptester *.o

I put out a new makefile which instead does:

        mv heaptester.o heaptester.oo
        rm -f heaptest heaptester *.o
        mv heaptester.oo heaptester.o


malloc

#include <stdlib.h>
void *malloc(size_t size);
The following will work (but has the limitation of a fixed size):
#define MAXLINE 1000 
char *getLine() { 
   char *p; 
   p = (char *)malloc(MAXLINE);
   if (p == NULL)
      return p;
   getline(p,MAXLINE); 
   return p; 
} 

Idea: allocate some space and start reading characters. If you run out of space, allocate more.

We can free up space that was created with malloc using free.

#include <stdlib.h>
void free(void *ptr);
ptr must have been returned by a previous malloc (or related function).


Here is an outline of our getLine:
#include <stdio.h>
#include <stdlib.h>
#define INIT_ALLOC 100
char *getLine() {
   int currentAlloc;
   int spaceLeft;
   char *line;
   char *templine;
   char *next;
   int c;

   currentAlloc = INIT_ALLOC;
   spaceLeft = currentAlloc;
   line = (char *)malloc(currentAlloc);
   if (line == NULL) return line;
   next = line;

   while ((c=getchar()) != '\n') {
      if (c == EOF) {
         *next = '\0';
         return line;
      }
      if (spaceLeft < ???) {   /* always leave room for some more */

/* reallocate here */

      }
      *next = c;
      next++;
      spaceLeft--;
   }
   *next = '\n';
   next++;
   *next = '\0';
   return line;
}


About the exam.

No Lab assignement due this week.

Lab Assignment 3 due Thursday, March 1: (along with Programming assignment 1)
Rewrite the allocation code below using realloc.



Here is what we added in class on Feb 15:

      if (spaceList == 2) {
         templine = (char *)malloc(2*currentAlloc);
         if (templine == NULL) {
            free(line);
            return NULL;
         }
         for (i=0;i<currentAlloc-2;i++)
            templine[i] = line[i];
         free(line);
         line = templine;
         spaceleft += currentAlloc;
         next = line + currentAlloc - 2;
         currentAlloc *= 2;
      }

Other forms of malloc

void *calloc(size_2 nelem, size_t elsize);
This allocates space for an array of nelem elements of size elsize.
The space is initialized to all zeros.
Note that space created by maloc is not initialized.

void *realloc(void *ptr, size_t size);
This changes the size of the block pointed to by ptr to size bytes and returns a pointer to the (possibly moved) block.
The contents will be unchanged up to the lesser of the new and old sizes.
If ptr is NULL, realloc() behaves like malloc() for the specified size.
If size is zero and ptr is not a null pointer, the object pointed to is freed.

Problem: Write a filter to ...

Many filters deal with lines and must read in the entire line before processing it.
The filter must be able to handle arbitrarily long lines.
What is wrong with using the getLine we just wrote?

Replace with:
char *getLine(char *line, int size);
Like the one we just wrote, but uses line for the initial storage, unless it is NULL.

What is wrong with this and how can we fix it?


5.5 Character Pointers and Functions

We have used string constants since the first class when we wrote
printf("hello world\n");
There are other ways to use string constants:
char amessage[] = "hello world\n";
char *pmessage = "hello world\n";

The difference is that amessage is an array and therefore a constant pointer. It will always point to the "hello world" string.
We can later do:
pmessage = "goodbye";
but my not do
amessage = "goodbye";

There is also a difference between the ways these are stored:

Here are several examples of a string copy function:

void strcpy(char s[], char t[]) {
   int i;
   i = 0;
   while ( t[i] != '\0' ) {
      s[i] = t[i];
      i++;
   }
   s[i] = '\0';
}

void strcpy(char s[], char t[]) {
   int i;
   i = 0;
   while ( (s[i] = t[i]) != '\0' ) 
      i++;
}

void strcpy(char *s, char *t) {
   int i;
   i = 0;
   while ( (*(s+i) = *(t+i)) != '\0' ) 
      i++;
}


void strcpy(char *s, char *t) {
   while ( (*s = *t) != '\0' ) {
      s++;
      t++;
   }
}

void strcpy(char *s, char *t) {
   while ( (*s++ = *t++) != '\0' ) ;
}

void strcpy(char *s, char *t) {
   while (*s++ = *t++) ;
}

There is a version of strcpy in the standard library and declared in string.h. This version returns the target string.

Another basic string function is strcmp which compares two strings.

   int strcmp(char *s, char *t);
Here is the man page description:

int strcmp(const char *s1, const char *s2);

The const indicate the the function does not change the strings.

Here is an array implementation:

int strcmp(char *s, char *t) {
   int i;
   for (i=0; s[i] == t[i]; i++)
      if (s[i] == '\0')
         return 0;
   return s[i] - t[i];
}
Here is a pointer version:
int strcmp(char *s, char *t) {
   for ( ; *s == *t; s++, t++)
      if (*s == '\0')
         return 0;
   return *s - *t;
}


About the exam ...


5.6 Pointer Arrays; Pointers to Pointers

In Java, command line parameters are handled with an array of Strings:
void main(String[] arg);
To do the same thing we would need an array of pointers to characters, such as:
   #define MAXLINES 100
   char *lineptr[MAXLINES];
Let us write a sorting routine to sort an array of strings.

We will prepare by sorting an array of integers:

   void bubblesortint(int vals[], int size) {
      int i;
      int j;
      int temp;
      for (i=0;i<size-1;i++)
         for (j=0;j<size-i-1;j++)
            if (vals[j] > vals[j+1]) {
               temp = vals[j];
               vals[j] = vals[j+1];
               vals[j+1] = temp;
            }
   }
Suppose that bubblesortint stores its parameters at 1000 and 1004 and its other automatic variables at 1008, 1012, and 1016. Assume the memory looks like this just after the function has been called:
addressvalueaddressvalue
10002000200020
1004 5200417
10081022008 34
1012103201211
101610420167
The values to be sorted:
20 17 34 11 7

After first pass:
addressvalueaddressvalue
10002000200017
1004 5200420
100812008 11
101242012 7
101634201634
The values to be sorted:
17 20 11 7 34

After second pass:
addressvalueaddressvalue
10002000200017
1004 5200411
1008 22008 7
1012 3201220
1016 20201634
The values to be sorted:
17 11 7 20 34

After third pass:
addressvalueaddressvalue
10002000200011
1004 52004 7
1008 3200817
1012 2201220
1016 17201634
The values to be sorted:
11 7 17 20 34

After fourth pass:
addressvalueaddressvalue
100020002000 7
1004 5200411
1008 3200817
1012 1201220
1016 11201634
The values to be sorted:
7 11 17 20 34

Now we will do a sort using pointers to integers:

   void bubblesortintpointer(int *vals[], int size) {
      int i;
      int j;
      int temp;
      for (i=0;i<size-1;i++)
         for (j=0;j<size-i-1;j++)
            if (*vals[j] > *vals[j+1]) {
               temp = *vals[j];
               *vals[j] = *vals[j+1];
               *vals[j+1] = temp;
            }
   }
Suppose that bubblesortintpointer stores its parameters at 1000 and 1004 and its other automatic variables at 1008, 1012, and 1016. Assume the memory looks like this just after the function has been called:
addressvalueaddressvalueaddressvalue
1000150015002012200034
1004 5150420082004 7
100810215082000200817
101210315122016201220
101610415162004201611
The values to be sorted:
20 17 34 11 7

After the first pass it looks like this:
addressvalueaddressvalueaddressvalue
1000150015002012200011
1004 515042008200434
1008 115082000200820
1012 415122016201217
1016 34151620042016 7
The values to be sorted:
17 20 11 7 34

After the second pass it looks like this:
addressvalueaddressvalueaddressvalue
10001500150020122000 7
1004 515042008200434
1008 215082000200811
1012 315122016201217
1016 2015162004201620
The values to be sorted:
17 11 7 20 34

After the third pass it looks like this:
addressvalueaddressvalueaddressvalue
1000150015002012200017
1004 515042008200434
1008 3150820002008 7
1012 415122016201211
1016 1715162004201620
The values to be sorted:
11 7 17 20 34

After the fourth pass it looks like this:
addressvalueaddressvalueaddressvalue
1000150015002012200017
1004 515042008200434
1008 415082000200811
1012 5151220162012 7
1016 1115162004201620
The values to be sorted:
7 11 17 20 34

Now we will look at a different way of sorting the array with pointers:

   void bubblesortintpointerpointer(int *vals[], int size) {
      int i;
      int j;
      int *temp;
      for (i=0;i<size-1;i++)
         for (j=0;j<size-i-1;j++)
            if (*vals[j] > *vals[j+1]) {
               temp = vals[j];
               vals[j] = vals[j+1];
               vals[j+1] = temp;
            }
   }
Instead of sorting the values stored at 2000 we sort the pointers stored at 1500.

Suppose that bubblesortintpointerpointer stores its parameters at 1000 and 1004 and its other automatic variables at 1008, 1012, and 1016. Assume the memory looks like this just after the function has been called:
Note: This is exactly the same as in the previous example.
addressvalueaddressvalueaddressvalue
1000150015002012200034
1004 5150420082004 7
100810215082000200817
101210315122016201220
101610415162004201611
The values to be sorted:
20 17 34 11 7

After the first pass it looks like this:
addressvalueaddressvalueaddressvalue
1000150015002008200034
1004 5150420122004 7
1008 115082016200817
1012 415122004201220
1016200015162000201611
The values to be sorted:
17 20 11 7 34

After the second pass it looks like this:
addressvalueaddressvalueaddressvalue
1000150015002008200034
1004 5150420162004 7
1008 215082004200817
1012 315122012201220
1016201215162000201611
The values to be sorted:
17 11 7 20 34

After the third pass it looks like this:
addressvalueaddressvalueaddressvalue
1000150015002016200034
1004 5150420042004 7
1008 315082008200817
1012 215122012201220
1016200815162000201611
The values to be sorted:
11 7 17 20 34

After the fourth pass it looks like this:
addressvalueaddressvalueaddressvalue
1000150015002004200034
1004 5150420162004 7
1008 415082008200817
1012 115122012201220
1016201615162000201611
The values to be sorted:
7 11 17 20 34

What is the advantage of the third method over the second?
If integers and pointers are the same size, there is no advantage.
What if the integers were 8 bytes or 16 bytes or 16,000 bytes?

Here is the third sort applied to doubles:

   void bubblesortdoublepointerpointer(double *vals[], int size) {
      int i;
      int j;
      double *temp;
      for (i=0;i<size-1;i++)
         for (j=0;j<size-i-1;j++)
            if (*vals[j] > *vals[j+1]) {
               temp = vals[j];
               vals[j] = vals[j+1];
               vals[j+1] = temp;
            }
   }

What is wrong with the following to convert it to strings:

 
   void bubblesortcharpointerpointer(char *vals[], int size) { 
      int i; 
      int j; 
      char *temp; 
      for (i=0;i<size-1;i++) 
         for (j=0;j<size-i-1;j++) 
            if (*vals[j] > *vals[j+1]) { 
               temp = vals[j]; 
               vals[j] = vals[j+1]; 
               vals[j+1] = temp; 
            } 
   } 

Here is the right way:

  
   #include <string.h>
   void bubblesortstringpointerpointer(char *vals[], int size) {  
      int i;  
      int j;  
      char *temp;  
      for (i=0;i<size-1;i++) 
         for (j=0;j<size-i-1;j++) 
            if (strcmp(vals[j],vals[j+1]) > 0) {  
               temp = vals[j]; 
               vals[j] = vals[j+1];  
               vals[j+1] = temp;  
            } 
   } 


5.7 Multi-dimensional Arrays

C does not have multi-dimensional arrays.

This should be familiar because Java does not either.

What these both have are arrays whose elements are arrays, and these behave like 2-dimensional arrays.

Here is an example from the book that can be used to calculate the number of days in a month:

int daytab[2][13] = {
   {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
   {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
};
Note that January has index 1, not 0.
Also note that this array has 2 rows and 13 columns.

The array is stored by rows, so the first row is stored in memory followed by the second row.

If integers take up 4 bytes and the array starts at location 1000, then daytab[0][0] is stored in locations 1000-1003 and daytab[0][1] is stored in locations 1004-1007.
daytab[1][0] is stored in locations 1052-1055.

Elements of 2-dimensional arrays are accessed with two pairs of brackets:
x = daytab[1][10];

The following is incorrect:
x = daytab[1,10];

How does the C compiler calculate the address of daytab[a][b]:

    daytab + sizeof(int)*(13*a + b)
Note that the compiler needs to know the number of columns, but not the number of rows.

What you write a function which takes this 2-dimensional array as a parameter. you could do its definition as
f(int daytab[2][13]) { ...
or
f(int daytab[][13]) { ...
but not
f(int daytab[][]) { ...


5.8 Initialization of Pointer Arrays

Arrays of strings are usually implemented as arrays of character pointers.
Here is an example of a function that uses an array of pointers:
char *month_name(int n) {
   static char *name[] = {
      "Illegal month",
      "January", "February", "March", "April",
      "May", "June", "July", "August",
      "September", "October", "November", "December"
   };
   return ( n<1 || n>12 ) ? name[0] : name[n];
}
What does the static do?
Why do we want to do it?


5.9 Pointers vs. Multi-dimensional Arrays

Here is a program which compares using arrays of pointers with arrays of characters:
#include <stdio.h>

int main() {

   char *name_pointer[] = {
      "Illegal month", "Jan.", "Feb."
   };

   char name_array[][13] = {
      {"Illegal month"},{"Jan."},{"Feb."}
   };

   printf("name_pointer[0] is %s\n",name_pointer[0]);
   printf("name_pointer[1] is %s\n",name_pointer[1]);
   printf("name_pointer[2] is %s\n",name_pointer[2]);
   printf("name_pointer[0] is stored at %p\n",&(name_pointer[0]));
   printf("name_pointer[1] is stored at %p\n",&(name_pointer[1]));
   printf("name_pointer[2] is stored at %p\n",&(name_pointer[2]));
   printf("name_pointer[0] points to    %p\n",name_pointer[0]);
   printf("name_pointer[1] points to    %p\n",name_pointer[1]);
   printf("name_pointer[2] points to    %p\n",name_pointer[2]);
   printf("\n");
   printf("name_array[0]   is %s\n",name_array[0]);
   printf("name_array[1]   is %s\n",name_array[1]);
   printf("name_array[2]   is %s\n",name_array[2]);
   printf("name_array[0]   is stored at %p\n",name_array[0]);
   printf("name_array[1]   is stored at %p\n",name_array[1]);
   printf("name_array[2]   is stored at %p\n",name_array[2]);

   return 0;
}
Here is the output generated by this program:
name_pointer[0] is Illegal month
name_pointer[1] is Jan.
name_pointer[2] is Feb.
name_pointer[0] is stored at effffa20
name_pointer[1] is stored at effffa24
name_pointer[2] is stored at effffa28
name_pointer[0] points to    20db8
name_pointer[1] points to    20dc8
name_pointer[2] points to    20dd0

name_array[0]   is Illegal monthJan.
name_array[1]   is Jan.
name_array[2]   is Feb.
name_array[0]   is stored at effff9f9
name_array[1]   is stored at effffa06
name_array[2]   is stored at effffa13


You can practice writing some of the string functions from the standard library. Their descriptions can be found here and here.


5.10 Command-line Arguments

In Java, command line arguments to a main application are passed as an array of strings. It is similar in C, except that C does not have a String type.

What is passed is an array of pointers to characters and the length of the array.

The prototype for this is:

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

Recall that when you specify and array as a function argument, what is passed is not a constant, but a variable containing the address of the array.

Suppose a program named echo is called with:

echo hello world
Then the arguments would look like this:

Here is a program that will echo its command line arguments, one per line:

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

   for (i=1;i<argc; i++)
      printf("%s\n",argv[i]);
   return 0;
}
Note that i starts at 1 so we do not output the name of the program.

Here is a version that outputs them on the same line:

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

   for (i=1;i<argc; i++)
      printf("%s%s",argv[i], (i < argc-1) ? " " : "");
   printf("\n");
   return 0;
}
Note the use of the conditional expression to avoid having a blank at the end of the list.

Here is another version which uses the fact that argv is a variable and not a constant.

int main(int argc, char *argv[]) {
   while (--argc > 0)
      printf("%s%s",*++argv, (argc > 1) ? " " : "");
   printf("\n");
   return 0;
}
Here is another way to write the printf statement:
   printf((argc > 1) ? "%s " : "%s", *++argv);

Here is an example of a program that will print out all lines from standard input which contain a given string. It returns a count of the number of lines which contain the search string.

#include <stdio.h>
#include <string.h>
#define MAXLINE 1000

int getline(char *line, int max);

int main(int argc, char *argv[]) {
   char line[MAXLINE];
   int found = 0;

   if (argc != 2) {
      printf("Usage %s pattern\n",argv[0]);
      return 0;
   }
   while (getline(line,MAXLINE) > 0) 
      if (strstr(line, argv[1]) != NULL) {
         printf("%s",line);
         found++;
      }
   return found;
}
Here is a version that uses your getLine instead of getline.
#include <stdio.h> 
#include <string.h>
 
char *getLine(); 
 
int main(int argc, char *argv[]) { 
   char *line;
   int found = 0; 
 
   if (argc != 2) {
      printf("Usage %s pattern\n",argv[0]);
      return 0;
   } 
   while ( (line = getLine()) != NULL)  {
      if (*line == '\0')
         return found;
      if (strstr(line, argv[1]) != NULL) {
         printf("%s",line); 
         found++; 
      } 
      free(line);
   }
   return found; 
} 
Many Unix commands take optional command line arguments which allow modification of the behavior of the command.

Suppose the program above is called find and we want the option of printing the lines not containing the string by using an optional command line argument -x (for except).

Further, suppose we want the option of including the line number that the string string was found on using -n.

All of the following should be valid:

   find pattern
   find -x pattern
   find -n pattern
   find -x -n pattern
   find -n -x pattern
   find -nx pattern
   find -xn pattern
There are two parts to handling this. The hard part is getting the command line parameters and setting the appropriate flags to indicate which parameters were present.

Suppose that number is true if line numbers are to be displayed and except is true if we want lines not containing the pattern. Also assume *argv points to the pattern and that lineno is a long initialized to 0.

while (getline(line, MAXLINE) > 0) {
   lineno++;
   if ((strstr(line, *argv) != NULL) != except) {
      if (number)
         printf("%lf:",lineno);
      printf("%s",line);
      found++;
   }
}
This contains some tricky code (from the book).
strstr returns NULL if the string was not found and a pointer to the located string otherwise.
The code in bold is true when the match is found.
We print it if it is found and except is false or if not found and except is true.

Here is the part of the program which sets the flags appropriately:

int main(int argc, char *argv[]) {
   char line[MAXLINE];
   long lineno = 0;
   int c;
   int except = 0;
   int number = 0;
   int found = 0;

   while (--argc > 0 && (*++argv)[0] == '-')
      while(c = *++argv[0])
         switch (c) {
         case 'x':
            except = 1;
            break;
         case 'n':
            number = 1;
            break;
         default:
            printf("find: illegal option %c\n",c);
            argc = 0;
            found = -1;
            break;
         }
   if (argc != 1)
      printf("Usage: find -x -n pattern\n");
   else
      ...
   return found;
}
Operator Precedence Table If you can understand this and write similar code which works (and other C programmers can understand), you are a 2-star programmer.