3

Original

Why cannot char (*)[N] be converted to char **?

What happened when char *[N] was converted to char (**)[N]? When I convert char *[N] to char (**)[N], it did work.

#include <stdio.h>

int main() {
    char *a[2]={"a", "a"};
    char b[2][2]={"b", "b"};
    // Expect "a, b"
    printf("%s, %s\n", a[0], b[0]); // 1. OK
    printf("%s, %s\n", *a, *b); // 2. OK
    printf("%s, %s\n", *(char **)a, *(char **)b); // 3. Segmentation fault
    printf("%s, %s\n", *(char **)a, *(char (*)[2])b); // 4. OK
    printf("%s, %s\n", *(char (*)[2])a, *(char (*)[2])b); // 5. Wrong output
    printf("%s, %s\n", *(char (**)[2])a, *(char (*)[2])b); // 6. Correct output
}

Edited

Why cannot char (*)[N] be converted to char **?

What happened when char *[N] was converted to char (**)[N]? When I convert char *[N] to char (**)[N], it did work.

Why do 5.0 and 5.1 have the same value and same address?

What's the value of 5.2?

#include <stdio.h>

#define N 10
// #define S "123456789"
   #define S "abcdefghi"

int main() {
    char *a[2]={S, S};
    char b[2][N]={S, S};
    // Expect "a, b"
    printf("1. %s, %s\n", a[0], b[0]); // 1. OK
    printf("2. %s, %s\n", *a, *b); // 2. OK
    printf("3. %s, %s\n", *(char **)a, *(char **)b); // 3. Segmentation fault
    printf("4. %s, %s\n", *(char **)a, *(char (*)[N])b); // 4. OK
    printf("5.0. %s, %s\n", (char (*)[N])a, *(char (*)[N])b); // 5.0. Wrong output
    printf("5.1. %s, %s\n", *(char (*)[N])a, *(char (*)[N])b); // 5.1. Wrong output
    printf("5.2. %s, %s\n", **(char (*)[N])a, *(char (*)[N])b); // 5.2. Segmentation fault
    printf("6.0. %s, %s\n", (char (**)[N])a, *(char (*)[N])b); // 6.0. Wrong output
    printf("6.1. %s, %s\n", *(char (**)[N])a, *(char (*)[N])b); // 6.1. Correct output
}
xqm32
  • 33
  • 4
  • 2
    Because a `char (*)[N]` is not, and cannot be, a `char **`. The types are not synonymous, nor convertible. You're seemingly new to C programming, so I'll let you in on a well-known but often-ignored secret: If you find yourself casting pointer types you're doing something wrong. The more seasoned you get the less likely that is 100% the case, but it is still fluttering in the 98+% arena even for professionals. Most of the casts in this [violate aliasing](https://stackoverflow.com/questions/98650/what-is-the-strict-aliasing-rule). – WhozCraig Jun 27 '21 at 06:33

1 Answers1

4

char (*)[N] and char** are very different things.

  • char (*)[N] is a pointer to the an array of N chars. Given char arr[N] and char (*parr)[N] = &arr, things are laid out in memory like this:
parr          arr
┌──────┐      ┌───┬───┬───┬───┬─────┬─────┐
│      │      │   │   │   │   │     │     │
│   ───┼─────►│ 0 │ 1 │ 2 │ 3 │ ... │ N-1 │
│      │      │   │   │   │   │     │     │
└──────┘      └───┴───┴───┴───┴─────┴─────┘
  • char** is a pointer to a char*. Given char arr[N], char* pchar = arr, and char** ppchar = &pchar, things are laid out in memory like this:
ppchar        pchar         arr
┌──────┐      ┌──────┐      ┌───┬───┬───┬───┬─────┬─────┐
│      │      │      │      │   │   │   │   │     │     │
│   ───┼─────►│   ───┼─────►│ 0 │ 1 │ 2 │ 3 │ ... │ N-1 │
│      │      │      │      │   │   │   │   │     │     │
└──────┘      └──────┘      └───┴───┴───┴───┴─────┴─────┘

As you can see, a char** needs a char* to point to, but there is no char* in the first situation. Only a char[N] and a pointer pointing directly to it. If you try to cast parr to a char** and dereference it, you'll end up in the land of undefined behavior trying to interpret the first several bytes of arr as a char*, which they are not.


So let's look at your specific cases. Your a and b arrays are laid out like this:

  ┌──────┬──────┬─────────┬──────┐    ┌──────┬───────┬─────────┬──────┐
  │0     │1     │         │N-1   │    │0     │1      │         │N-1   │
  │ 'a'  │ 'b'  │   ...   │ '\0' │    │ 'a'  │ 'b'   │   ...   │ '\0' │
  │      │      │         │      │    │      │       │         │      │
  └──────┴──────┴─────────┴──────┘    └──────┴───────┴─────────┴──────┘
    ▲                                   ▲
a   │                ┌──────────────────┘
┌───┼────────────┬───┼────────────┐
│0  │            │1  │            │
│   │            │   │            │
│                │                │
│                │                │
└────────────────┴────────────────┘

b
┌──────────────────────────────────┬──────────────────────────────────┐
│ 0                                │ 1                                │
│ ┌──────┬──────┬─────────┬──────┐ │ ┌──────┬──────┬─────────┬──────┐ │
│ │0     │1     │         │N-1   │ │ │0     │1     │         │N-1   │ │
│ │ 'a'  │ 'b'  │   ...   │ '\0' │ │ │ 'a'  │ 'b'  │   ...   │ '\0' │ │
│ │      │      │         │      │ │ │      │      │         │      │ │
│ └──────┴──────┴─────────┴──────┘ │ └──────┴──────┴─────────┴──────┘ │
│                                  │                                  │
└──────────────────────────────────┴──────────────────────────────────┘

That is, a is an array containing two pointers, each pointing to the first element of an array of chars and b is an array containing two arrays of two chars.

Note: In many of these cases I make assumptions about what the compiler will do in the face of undefined behavior. While the analysis I have here is something that an implementation may end up doing, undefined behavior is undefined. All logic goes out the window in the face of undefined behavior, and there are no constraints on what the compiler and/or your program can do when it encounters a situation for which the behavior is undefined.

  1. printf("1. %s, %s\n", a[0], b[0]): This is fine.
    • You index into a and obtain a char*
    • You index into b and obtain a char[N], which when passed to printf will decay into a char* to the first element of b[0].
  2. printf("2. %s, %s\n", *a, *b): This is fine.
    • In this context, a decays into a char** pointing to the first element of a. You dereference that to obtain a char*.
    • In this context, b decays into a char (*)[N] pointing to the first element of b. You dereference that to obtain a char[N], which when passed to printf will decay into a char* pointing to the first element of b[0].
  3. printf("3. %s, %s\n", *(char **)a, *(char **)b): This is not valid.
    • Casting a to char** is fine. This is the same conversion that happens implicitly in most contexts. After that, everything is exactly the same as situation (2).
    • Casting b to char** is not valid. There is no char* to point to. What happens is that b decays to a char (*)[N] which you then forcibly cast to char**. Dereferencing that pointer treats the first several bytes of b[0] as if it were a pointer to a char, but it's not. The behavior of your program in this situation is undefined.
  4. printf("4. %s, %s\n", *(char **)a, *(char (*)[N])b): This is fine, and exactly the same as situation (2), just with the implicit decay conversions specified explicitly.
    1. printf("5.0. %s, %s\n", (char (*)[N])a, *(char (*)[N])b): This is not valid.
      • Casting a to char (*)[N] is not valid. In this context, a decays to a char** which you then forcibly convert to a char (*)[N]. printf will then convert the char (*)[N] you passed it to a char*, but it doesn't point to a char, it points to a char*. printf will likely end up interpreting the bytes of the char* a[0] as if they were chars. Your program's behavior is undefined in this situation though.
    2. printf("5.1. %s, %s\n", *(char (*)[N])a, *(char (*)[N])b): This is not valid.
      • Casting a to char (*)[N] is not valid. In this context, a decays to a char** which you then forcibly convert to a char (*)[N]. When you dereference that you end up interpreting the first N bytes of the char* a[0] as if they were chars. The behavior of your program is undefined in this situation.
      • As mentioned before, casting b to char (*)[N] is fine, and is exactly the same as situation (2).
    3. printf("5.2. %s, %s\n", **(char (*)[N])a, *(char (*)[N])b): This is not valid.
      • Casting a to char (*)[N] is not valid. In this context, a decays to a char** which you then forcibly convert to a char (*)[N]. Dereferencing that yields a char[N]. Dereferencing that will cause it to decay into a char* that's actually pointing to the temporary char** that was created when a decayed. printf will likely end up trying to interpret those bytes as chars. In this case it likely ended up walking off the end of the currently mapped page leading to a segfault, but that result is in no way predictable. Your program's behavior is undefined in this situation, and anything can happen.
      • As mentioned before, casting b to char (*)[N] is fine, and is exactly the same as situation (2).
    1. printf("6.0. %s, %s\n", (char (**)[N])a, *(char (*)[N])b): This is not valid.
      • Casting a to char (**)[N] is not valid. What's happening is that a decays into a char**, which you then forcibly convert to char (**)[N] (pointer to pointer to array of N chars). printf will likely then interpret that char (**)[N] (which actually points to a char*, not a char (*)[N]) as a char* and end up interpreting the bytes of the char* a[0] as if they were chars. Your program's behavior is undefined in this situation.
      • As mentioned before, casting b to char (*)[N] is fine, and is exactly the same as situation (2).
    2. printf("6.1. %s, %s\n", *(char (**)[N])a, *(char (*)[N])b): This is not valid.
      • Casting a to char (**)[N] is not valid. What's happening is that a decays into a char**, which you then forcibly convert to char (**)[N]. You then dereference that and interpret the bits of the char* a[0] as if they were a char (*)[N]. You get the result you expected here because you have the same number of levels of indirection, and it just so happens that a[0] does point to the first element of a char[N]. A char (*)[N] pointing to an array and a char* pointing to that array's first element will usually have the exact same bit pattern, so when printf later interprets the char (*)[N] you passed to it as a char* it finds what you expected. That isn't guaranteed though; your program's behavior is undefined in this situation.
      • As mentioned before, casting b to char (*)[N] is fine, and is exactly the same as situation (2).
Miles Budnek
  • 28,216
  • 2
  • 35
  • 52
  • Thank you so much for such a detailed answer! After I asked this question, I found another problem. It was showed in the edited version of this question: Why do `5.0` and `5.1` have the same value and same address? What's the value of `5.2`? – xqm32 Jun 27 '21 at 08:29
  • @xqm32 I've updated my answer based on your edit. – Miles Budnek Jun 27 '21 at 09:00