Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why this simple program in C crashes (array VS pointer)

I have two files:

In file 1.c I have the following array:

char p[] = "abcdefg";

In file 0.c I have the following code:

#include <stdio.h>

extern char *p; /* declared as 'char p[] = "abcdefg";' in 1.c file */

int main()
{
    printf("%c\n", p[3]);   /* crash */
    return 0;
}

And this is the command line:

gcc  -Wall -Wextra     0.c  1.c

I know that extern char *p should've been: extern char p[];, but I just want an explanation of why it doesn't work in this particular case. While it works here:

int main()
{
    char a[] = "abcdefg";
    char *p = a;

    printf("%c\n", p[3]);   /* d */
    return 0;
}
like image 633
Bite Bytes Avatar asked Nov 28 '22 22:11

Bite Bytes


2 Answers

Your two examples are not comparable.

In your second example, you have

char a[] = "abcdefg";
char *p = a;

So a is an array, and p is a pointer. Drawing that in pictures, it looks like

      +---+---+---+---+---+---+---+---+
   a: | a | b | c | d | e | f | g | \0|
      +---+---+---+---+---+---+---+---+
        ^
        |
   +----|----+
p: |    *    |
   +---------+

And this is all fine; no problems with that code.

But in your first example, in file 1.c you define an array named p:

   +---+---+---+---+---+---+---+---+
p: | a | b | c | d | e | f | g | \0|
   +---+---+---+---+---+---+---+---+

You can name an array "p" if you want (the compiler certainly doesn't care), but then, over in file 0.c, you change your mind and declare that p is a pointer. You also declare (with the "extern" keyword) that p is defined somewhere else. So the compiler takes your word for it, and emits code that goes to location p and expects to find a pointer there -- or, in pictures, it expects to find a box, containing an arrow, that points somewhere else. But what it actually finds there is your string "abcdefg", only it doesn't realize it. It will probably end up trying to interpret the bytes 0x61 0x62 0x63 0x64 (that is, the bytes making up the first part of the string "abcdefg") as a pointer. Obviously that doesn't work.

You can see this clearly if you change the printf call in 0.c to

printf("%p\n", p);

This prints the value of the pointer p as a pointer. (Well, of course, p isn't really a pointer, but you lied to the compiler and told it that it was, so what you'll see is the result when the compiler treats it as a pointer, which is what we're trying to understand here.) On my system this prints

0x67666564636261

That's all 8 bytes of the string "abcdefg\0", in reverse order. (From this we can infer that I'm on a machine which (a) uses 64-bit pointers and (b) is little-endian.) So if I tried to print

printf("%c\n", p[3]);

it would try to fetch a character from location 0x67666564636264 (that is, 0x67666564636261 + 3) and print it. Now, my machine has a fair amount of memory, but it doesn't have that much, so location 0x67666564636264 doesn't exist, and therefore the program crashes when it tries to fetch from there.

Two more things.

If arrays are not the same as pointers, how did you get away with saying

char *p = a;

in your second example, the one I said was "all fine; no problems"? How can you assign an array on the right-hand side to a pointer on the left? The answer is the famous (infamous?) "equivalence between arrays and pointers in C": what actually happens is just as if you had said

char *p = &a[0];

Whenever you use an array in an expression, what you get is actually a pointer to the array's first element, just as I showed in the first picture in this answer.

And when you asked, "why it doesn't work, while it works here?", there were two other ways you could have asked it. Suppose we have the two functions

void print_char_pointer(char *p)
{
    printf("%s\n", p);
}

void print_char_array(char a[])
{
    printf("%s\n", a);
}

And then suppose we go back to your second example, with

char a[] = "abcdefg";
char *p = a;

and suppose that we call

print_char_pointer(a);

or

print_char_array(p);

If you try it, you'll find that there are no problems with either of them. But how can this be? How can we pass an array to a function that expects a pointer, when we call print_char_pointer(a)? And how can we pass a pointer to a function that expects an array, when we call print_char_array(p)?

Well, remember, whenever we mention an array in an expression, what we get is a pointer to the array's first element. So when we call

print_char_pointer(a);

what we get is just as if we had written

print_char_pointer(&a[0]);

What actually gets passed to the function is a pointer, which is what the function expects, so we're fine.

But what about the other case, where we pass a pointer to a function that's declared as if it accepts an array? Well, there's actually another tenet to the "equivalence between arrays and pointers in C". When we wrote

void print_char_array(char a[])

the compile treated it just as if we had written

void print_char_array(char *a)

Why would the compiler do such a thing? Why, because it knows that no array will ever be passed to a function, so it knows that no function will actually ever receive an array, so it knows that the function will receive a pointer instead. So that's the way the compiler treats it.

(And, to be very clear, when we talk about the "equivalence between arrays and pointers in C", we are not saying that pointers and arrays are equivalent, just that there is this special equivalence relationship between them. I've mentioned two of the tenets of that equivalence already. Here are all three of them, for reference: (1) Whenever you mention the name of an array in an expression, what you automatically get is a pointer to the array's first element. (2) Whenever you declare a function that seems to accept an array, what it actually accepts is a pointer. (3) Whenever you use the "array" subscripting operator, [], on a pointer, as in p[i], what you actually get is just as if you had written *(p + i). And, in fact, if you think about it carefully, due to tenet (1), even when you use the array subscripting operator on something that looks like an array, you're actually using it on a pointer. But that's a pretty strange notion, which you don't have to worry about if you don't want to, because it just works.)

like image 162
Steve Summit Avatar answered Dec 15 '22 15:12

Steve Summit


Because arrays are not pointers. You tell the program "elsewhere I have a char pointer", but you actually don't have one - you have an array.

An array will decay into a pointer when used in an expression, but that doesn't mean that an array is a pointer. For more info see Is an array name a pointer?.

In your second example you have both an array and a pointer, two separate variables, so it is a different case.

like image 22
Lundin Avatar answered Dec 15 '22 15:12

Lundin