2
 char string1[3][4]={"koo","kid","kav"}; //This is a 2D array

 char  * string[3]={"koo","kid","kav"}; //This is an array of 3 pointers pointing to 1D array as strings are stored as arrays in memory
 char (*string1Ptr)[4]=string1; //This is a pointer to a 1D array of 4 characters

//I want to know differences between string1Ptr(pointer to array mentioned in question) and string(array of pointers mentioned in question). I only typed string1 here to give string1Ptr an address to strings

Besides the fact that string can point to strings of any size and string1Ptr can only point to strings of size 4 only(otherwise pointer arithmetic would go wrong), I don't see any differences between them.

For example,

   printf("%s\n", string1[2]);   // All print the same thing, ie, the word "kav"
   printf("%s\n", string1Ptr[2]);
   printf("%s\n", string[2]);

They all seem to perform the same pointer arithmetic.(My reason for assuming string and string1Ptr are almost similar besides for the difference I stated above)

So what are the differences between string and string1Ptr? Any reason to use one over the other?

PS: I'm a newbie so please go easy on me. Also, I did check C pointer to array/array of pointers disambiguation, it didn't seem to answer my question.

Leon
  • 346
  • 3
  • 15

2 Answers2

4
char string1[3][4]={"koo","kid","kav"}; //This is a 2D array

char  * string[3]={"koo","kid","kav"}; //This is an array of 3 pointers pointing to 1D array as strings are stored as arrays in memory
char (*string1Ptr)[4]=string1; //This is a pointer to a 1D array of 4 characters

Besides the fact that string can point to strings of any size and string1Ptr can only point to strings of size 4 only(otherwise pointer arithmetic would go wrong), I don't any differences between them.

They are absolutely, fundamentally different, but C goes to some trouble to hide the distinction from you.

string is an array. It identifies a block of contiguous memory wherein its elements are stored. Those elements happen to be of type char * in this example, but that's a relatively minor detail. One can draw an analogy here to a house containing several rooms -- the rooms are physically part of and exist inside the physical boundaries of the house. I can decorate the rooms however I want, but they always remain the rooms of that house.

string1Ptr is a pointer. It identifies a chunk of memory whose contents describe how to access another, different chunk of memory wherein an array of 4 chars resides. In our real estate analogy, this is like a piece of paper on which is written "42 C Street, master bedroom". Using that information, you can find the room and redecorate it as you like, just as in the other case. But you can also replace the paper with a locator for a different room, maybe in a different house, or with random text, or you can even burn the whole envelope, without any of that affecting the room on C Street.

string1, for its part, is an array of arrays. It identifies a block of contiguous memory where its elements are stored. Each of those elements is itself an array of 4 chars, which, incidentally, happens to be just the type of object to which string1Ptr can point.

For example,

printf("%s\n", string1[2]);   // All print the same thing, ie, the word "kav"
printf("%s\n", string1Ptr[2]);
printf("%s\n", string[2]);

They all seem to perform the same pointer arithmetic.(My reason for assuming string and string1Ptr are almost similar besides for the difference I stated above)

... and that is where C hiding the distinction comes in. One of the essential things to understand about C arrays is that in nearly all expressions,* values of array type are silently and automatically converted to pointers [to the array's first element]. This is sometimes called pointer "decay". The indexing operator is thus an operator on pointers, not on arrays, and indeed it does have similar behavior in your three examples. In fact, the pointer type to which string1 decays is the same as the type of string1Ptr, which is why the initialization you present for the latter is permitted.

But you should understand that the logical sequence of operations is not the same in those three cases. First, consider

printf("%s\n", string1Ptr[2]);

Here, string1Ptr is a pointer, to which the indexing operator is directly applicable. The result is equivalent to *(string1Ptr + 2), which has type char[4]. As a value of array type, that is converted to a pointer to the first element (resulting in a char *).

Now consider

printf("%s\n", string1[2]);

string1 is an array, so first it is converted to a pointer to its first element, resulting in a value of type char(*)[4]. This is the same type as string1Ptr1, and evaluation proceeds accordingly, as described above.

But this one is a bit more different:

printf("%s\n", string[2]);

Here, string is a pointer, so the indexing operation applies directly to it. The result is equivalent to *(string + 2), which has type char *. No automatic conversions are performed.

Any reason to use one over the other?

Many, in both directions, depending on your particular needs at the time. Generally speaking, pointers are more flexible, especially in that they are required for working with dynamically allocated memory. But they suffer from the issues that

  • a pointer may be in scope, but not point to anything, and
  • declaring a pointer does not create anything for it to point to. Also,
  • even if a pointer points to something at one time during an execution of the program, and its value is not subsequently written by the program, it can nevertheless stop pointing to anything. (This most often is a result of the pointer outliving the object to which it points.)

Additionally, it can be be both an advantage and a disadvantage that

  • a pointer can freely be assigned to point to a new object, any number of times during its lifetime.

Generally speaking, arrays are easier to use for many purposes:

  • declaring an array allocates space for all its elements. You may optionally specify initial values for them at the point of declaration, or in some (but not all) cases avail yourself of default initialization.
  • the identifier of an array is valid and refers to the array wherever it is in scope.
  • Optionally, if an initializer is provided then an array declaration can use it to automatically determine the array dimension(s).

* But only nearly all. There are a few exceptions, with the most important being the operand of a sizeof operator.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • So then the main difference is internal workings right(order of implicit conversions and pointer arithmetic if any)? Also, "pointer type to which `string1` decays is the same as the type of `string1Ptr`, which is why the initialization you present for the latter is permitted." is the reason why we can use pointers and arrays almost interchangeably right? Also for your last 2 points: 1) What are the cases default initialization can help me? 2) "automatically determine the array dimension(s)." refers to the highest dimension only right? – Leon Jan 04 '20 at 03:20
  • I meant to say "pointer and array notation almost interchangeably" – Leon Jan 04 '20 at 06:52
  • No, @Leon, you're not quite getting it. We do not "use pointer and array notation almost interchangeably". Rather, if we use arrays at all then it is nigh-inescapable that we use them *together with* pointers. In particular, the indexing operator (`[]`) is strictly a pointer operator in C -- its use with arrays operates via the automatic conversions from array to pointer. There are almost no operators directly on arrays -- `sizeof`, `_Alignof`, and `&` (address-of) are the only ones. – John Bollinger Jan 04 '20 at 13:42
  • I spent a lot of space in the middle of this answer explaining the evaluation of the different expressions and why your program can structure data in different ways yet produce the same output, but I *led* with the main differences: they are about the structure and layout of the data. These have numerous implications for distinctions between the uses of pointers and arrays, some of the more important of which I detailed at the end. – John Bollinger Jan 04 '20 at 13:44
  • (1) Default initialization does not apply to non-`static`, block-scope variables. You can avail yourself of default initialization for variables that appear at file scope or that are `static`. (2) Yes, only one array dimension, the lexically leftmost, can be determined automatically. Note well that in real-world programs, one-dimensional arrays are used far, far more frequently than any other kind, and arrays with more than two dimensions appear only very rarely. – John Bollinger Jan 04 '20 at 13:50
0

The difference between string1 and string is the same as the difference between:

char s1[4] = "foo";
char *s2 = "foo";

s1 is a writable array of 4 characters, s2 is a pointer to a string literal, which is not writable. See Why do I get a segmentation fault when writing to a string initialized with "char *s" but not "char s[]"?.

So in your example, it's OK to do string1[0][0] = 'f'; to change string1[0] to "foo", but string[0][0] = 'f'; causes undefined behavior.

Also, since string is an array of pointers, you can reassign those pointers, e.g. string[0] = "abc";. You can't assign to string1[0] because the elements are arrays, not pointers, just as you can't reassign s1.

The reason that string1Ptr works is because the string1 is a 2D array of char, which is guaranteed to be contiguous. string1Ptr is a pointer to an array of 4 characters, and when you index it you increment by that number of characters, which gets you to the next row of the string1 array.

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • If I'm not wrong this seems to answer "Differences between `string` and `string1` " rather than the question- "Differences between `string` and `string1Ptr` ". Or are you trying to imply that the difference between `string` and `string1Ptr` is that the contents to what `string1Ptr` is pointing can be modified but you can't do that for `string`(based on your last paragraph)? Apologies if there is a mistake on my part. – Leon Jan 04 '20 at 01:05
  • I thought he was asking about all of them. – Barmar Jan 04 '20 at 01:07