简体   繁体   中英

Pointers and Arrays — Learn C the hard way

This question is from Learn C the Hard Way by Zed Shaw. It's about pointers and arrays. We are given some code here:

#include <stdio.h>

int main(int argc, char *argv[])
{
  // create two arrays we care about
  int ages[] = {23, 43, 12, 89, 2};
  char *names[] = {
      "Alan", "Frank",
      "Mary", "John", "Lisa"
  };

  // safely get the size of ages
  int count = sizeof(ages) / sizeof(int);
  int i = 0;

  // first way using indexing
  for(i = 0; i < count; i++) {
      printf("%s has %d years alive.\n",
              names[i], ages[i]);
  }

  printf("---\n");

  // setup the pointers to the start of the arrays
  int *cur_age = ages;
  char **cur_name = names;

  // second way using pointers
  for(i = 0; i < count; i++) {
      printf("%s is %d years old.\n",
            *(cur_name+i), *(cur_age+i));
  }

  printf("---\n");

  // third way, pointers are just arrays
  for(i = 0; i < count; i++) {
      printf("%s is %d years old again.\n",
              cur_name[i], cur_age[i]);
  }

  printf("---\n");

  // fourth way with pointers in a stupid complex way
  for(cur_name = names, cur_age = ages;
          (cur_age - ages) < count;
          cur_name++, cur_age++)
  {
      printf("%s lived %d years so far.\n",
              *cur_name, *cur_age);
  }

  return 0;
}

The directive is to " rewrite all the array usage in this program so that it's pointers. " Does this mean to do something like?

int *ptr;
ptr = &ages[0]

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.

It probably means something like what you suggested, yeah.

Keep in mind, though, that ages is already an int pointer ( int * ) -- an array, in C, is just a bunch of things all next to each other in memory. A variable that represents that array is just a pointer to the first element in that array, and the [] operator is a dereference.

You can think of it like this:

There's a chunk of memory somewhere as your program is running that contains
|...| 23 | 43 | 12 | 89 | 2 |...|
Where each box represents enough space to hold one int .
The variable ages in your program, then, is just a pointer that holds the address of the first element in that chunk. It "points to" the 23 , and has type int* . If you dereference that, you'll find that *ages evaluates to 23. Similarly, if you take that address and "skip" one int -size forward, you'll get 43 . In code, this would look like
*(ages + 1 * sizeof(int))
where you can replace the 1 with however many elements you want to skip. Because this is really ugly and confusing, C provides you a nice way of doing exactly the same thing: the [] operator. In general,

some_array[n] == *(some_array + n * sizeof(array_element_type))

Hope that helps, and good luck learning C! Make sure you take the time to really understand the equality of arrays and pointers; it will make a lot of stuff a lot harder later on if you don't.

My guess is that it means

  1. Allocate memory for each array using malloc() and release the memory using free() .

  2. Use pointer arithmetic in all for loops.

The task of "rewriting the code with pointer usage instead of array usage" is not sufficiently clearly formulated to make sense. In C language 99.9% (just an informal number) of array functionality is based on the implicit array-to-pointer conversion, meaning that virtually every time you use arrays, you use pointers as well. There's no way around it.

In other words, formally there's really no need to rewrite anything.

If you rewrite your code by doing

int *ptr = &ages[0];

and using ptr in place of ages , you will simply make explicit something that was already present in your code implicitly. If that's what is really meant by that task, then you can certainly do that. But I don't see much point in such a reduntant excercise.

Here's a way to change ages and names to be pointers without using dynamic allocation.

  // create two arrays we care about
  const char *ages = "\x17\x2b\x0c\x59\x02";
  const char (*names)[6] = (void *)
      "Alan\0\0" "Frank\0" "Mary\0\0" "John\0\0" "Lisa\0\0";

  // safely get the size of ages
  int count = strlen(ages);

  //...

  // setup the pointers to the start of the arrays
  const char *cur_age = ages;
  const char (*cur_name)[6] = names;

names and cur_name are both pointer types, although they do point to arrays.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM