Let me start off by saying something a little off topic:
- I don't think this is a very good book. I think it confuses some topics to make them seem harder than they really are. For a better advanced C book, I would recommend Deep C Secrets by Peter van der Linden, and for a beginner's book, I'd recommend the original K & R
Anyway, it looks like you're looking at the extra credit exercises from this chapter.
- Another aside- I don't think this is an especially sensible exercise for learning (another answer pointed out the question isn't formed to make sense), so this discussion is going to get a little complex. I would instead recommend the exercises from Chapter 5 of K & R.
First we need to understand that pointers are not the same as arrays. I've expanded on this in another answer here, and I'm going to borrow the same diagram from the C FAQ. Here's what's happening in memory when we declare an array or a pointer:
char a[] = "hello"; // array
+---+---+---+---+---+---+
a: | h | e | l | l | o |\0 |
+---+---+---+---+---+---+
char *p = "world"; // pointer
+-----+ +---+---+---+---+---+---+
p: | *======> | w | o | r | l | d |\0 |
+-----+ +---+---+---+---+---+---+
So, in the code from the book, when we say:
int ages[] = {23, 43, 12, 89, 2};
We get:
+----+----+----+----+---+
ages: | 23 | 43 | 12 | 89 | 2 |
+----+----+----+----+---+
I'm going to use an illegal statement for the purpose of explanation - if we could have said:
int *ages = {23, 43, 12, 89, 2}; // The C grammar prohibits initialised array
// declarations being assigned to pointers,
// but I'll get to that
It would have resulted in:
+---+ +----+----+----+----+---+
ages: | *=====> | 23 | 43 | 12 | 89 | 2 |
+---+ +----+----+----+----+---+
Both of these can be accessed the same way later on - the first element "23" can be accessed by ages[0]
, regardless of whether it's an array or a pointer. So far so good.
However, when we want to get the count we run in to problems. C doesn't know how big arrays are - it only knows how big (in bytes) the variables it knows about are. This means, with the array, you can work out the size by saying:
int count = sizeof(ages) / sizeof(int);
or, more safely:
int count = sizeof(ages) / sizeof(ages[0]);
In the array case, this says:
int count = the number of bytes in (an array of 6 integers) /
the number of bytes in (an integer)
which correctly gives the length of the array. However, for the pointer case, it will read:
int count = the number of bytes in (**a pointer**) /
the number of bytes in (an integer)
which is almost certainly not the same as the length of the array. Where pointers to arrays are used, we need to use another method to work out how long the array is. In C, it is normal to either:
Remember how many elements there were:
int *ages = {23, 43, 12, 89, 2}; // Remember you can't actually
// assign like this, see below
int ages_length = 5;
for (i = 0 ; i < ages_length; i++) {
or, keep a sentinel value (that will never occur as an actual value in the array) to indicate the end of the array:
int *ages = {23, 43, 12, 89, 2, -1}; // Remember you can't actually
// assign like this, see below
for (i = 0; ages[i] != -1; i++) {
(this is how strings work, using the special NUL value '\0' to indicate the end of a string)
Now, remember that I said you can't actually write:
int *ages = {23, 43, 12, 89, 2, -1}; // Illegal
This is because the compiler won't let you assign an implicit array to a pointer. If you REALLY want to, you can write:
int *ages = (int *) (int []) {23, 43, 12, 89, 2, -1}; // Horrible style
But don't, because it is extremely unpleasant to read. For the purposes of this exercise, I would probably write:
int ages_array[] = {23, 43, 12, 89, 2, -1};
int *ages_pointer = ages_array;
Note that the compiler is "decaying" the array name to a pointer to it's first element there - it's as if you had written:
int ages_array[] = {23, 43, 12, 89, 2, -1};
int *ages_pointer = &(ages_array[0]);
However - you can also dynamically allocate the arrays. For this example code, it will become quite wordy, but we can do it as a learning exercise. Instead of writing:
int ages[] = {23, 43, 12, 89, 2};
We could allocate the memory using malloc:
int *ages = malloc(sizeof(int) * 5); // create enough space for 5 integers
if (ages == NULL) {
/* we're out of memory, print an error and exit */
}
ages[0] = 23;
ages[1] = 43;
ages[2] = 12;
ages[3] = 89;
ages[4] = 2;
Note that we then need to free ages
when we're done with the memory:
free(ages);
Note also that there are a few ways to write the malloc call:
int *ages = malloc(sizeof(int) * 5);
This is clearer to read for a beginner, but generally considered bad style because there are two places you need to change if you change the type of ages
. Instead, you can write either of:
int *ages = malloc(sizeof(ages[0]) * 5);
int *ages = malloc(sizeof(*ages) * 5);
These statements are equivalent - which you choose is a matter of personal style. I prefer the first one.
One final thing - if we're changing the code over to use arrays, you might look at changing this:
int main(int argc, char *argv[]) {
But, you don't need to. The reason why is a little subtle. First, this declaration:
char *argv[]
says "there is an array of pointers-to-char called argv". However, the compiler treats arrays in function arguments as a pointer to the first element of the array, so if you write:
int main(int argc, char *argv[]) {
The compiler will actually see:
int main(int argc, char **argv)
This is also the reason that you can omit the length of the first dimension of a multidimensional array used as a function argument - the compiler won't see it.