2

I have a string located in an array of chars, and I want to parse it and increment the pointer. So I created a func that gets a (char **) as an argument, and when I pass &str there's an error - so I declared a pointer to this array, and passed &p - and it worked.

Now I'm wondering why it didn't work with the array.

For example:

void func(char **str);

int main()
{
    char str[100] = {'\0'};
    char *p = str;
    func(&str);  // vs func(&p); 
}

the error is:

test2.c:194:29: error: passing argument 1 of ‘PrintVariables’ from incompatible pointer type [-Wincompatible-pointer-types]
  194 |       size = PrintVariables(&line, size);
      |                             ^~~~~
      |                             |
      |                             char (*)[100]
  

I'm trying to understand the difference between char ** and char (*)[100], because obviously these are different types, and I don't understand why and what is the use case for each.

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
MatanCode
  • 37
  • 3
  • @PetrSkocik it's more about pointers, i'm asking about specific case. didn't see an answer there or didn't quite understand – MatanCode May 26 '23 at 16:10
  • 1
    In short: arrays are not pointers. They're merely contiguous sequences for which a pointer to the first element is created on the spot whenever an attempt is made to use the array as a value (except inside of special contexts such as sizeof or typeof). Consequently the address of an array cannot be treated as a pointer to a first-element-pointer, because the array's memory doesn't start with a first element pointer. It starts with the first element. – Petr Skocik May 26 '23 at 16:12
  • `&str` is not a "pointer to a pointer", but `&p` is. – Weather Vane May 26 '23 at 16:13
  • `char **` [is "pointer to pointer to char"](https://cdecl.org/?q=char+**var) whereas `char (*)[100]` [is "pointer to array 100 of char"](https://cdecl.org/?q=char+%28*var%29%5B100%5D). – Andreas Wenzel May 26 '23 at 16:15

1 Answers1

4

char ** is a pointer to a pointer to char; char (*)[100] is a pointer to an array of 100 elements of char. The parentheses are needed because char *[100] would be an array of 100 pointers to char.

In your code, str is of type char [100]. In the statement char *p = str;, str first automatically decays to type char * and then gets assigned to p (also of type char *). Hence &p is of type char **.

Note that writing func((char **)&str); (in order to please func) would be problematic:

  1. &str is of type char (*)[100]. That is, it is a pointer to char [100] (array [100] of char), and it is as large as the address of an object of type char [100] needs to be. This is standardly the same as sizeof(void *), and in most cases all pointers have this size, but according to the standard, this doesn't absolutely have to be the case. Here specifically, pointers to char [100] and pointers to char * needn't be compatible. Hence, casting char (*)[100] to char ** doesn't necessarily work.
  2. str is char-aligned (1-aligned), while anything of type char * has stronger pointer-based alignment requirements (say, 4 or 8) [credit to Petr Skocik]. Hence, &str can be any address, while an address of type char ** would need to be divisible by 4 or 8. Therefore, casting &str to char ** is a non-starter. Conceptually, such a cast implies that the result can be dereferenced twice.
    • After the initial dereferencing of &str, we get str, which (being of type char [100]) is a 1-aligned object and decays to &str[0] (the address of the first char in str). Further dereferencing gives us the bare initial char str[0].
    • But for the hypothetical expression (char **)&str (and assuming alignment wasn't an issue for this cast), initial dereferencing would give us *(char **)&str, which would point to the beginning of the string (char [100]) but nominally be of type char *. For further dereferencing, we would need to interpret the 4 or 8 bytes starting from that address as the address of another char, which is unlikely to succeed and semantically not what we want anyway. (Because char * is not an array type, there is no decay to another pointer (pointing to the same address) that lets us dereference things further, like we could do repeatedly for a multidimensional array.)

Two methods work:

  • Using p and func(&p); (from your code) works fine.
  • An alternative [credit to Petr Skocik], is to use a compound literal, (char *){str}, in the function call func( &(char *){str} );. Even though (char *){str} looks like a cast, it is not; it is an anonymous object of type char * initialized to str. It is legal to take the address of a compound literal. That is, this is like using an anonymous kind of p.

Compound literals are described in section 6.5.2.5 of the C17 standard draft. They exist since C99.

Lover of Structure
  • 1,561
  • 3
  • 11
  • 27
  • Thanks. Can you explain this in terms of memory? why do I have to add this casting so it will work? – MatanCode May 26 '23 at 16:24
  • @LoverofStructure Also the cast isn't problematic because the pointers could potentially have different sizes. It's problematic even if all pointers have the same size and representation and the reason it is problematic is (1) pointer target alignment (it's UB to cast a misaligned address to a pointer type requiring alignment) and (2) strict aliasing (can't use a `char**` to access a char that isn't effectively there (the UB would then probably happen inside the function)). – Petr Skocik May 26 '23 at 17:16
  • @PetrSkocik I am still confused about alignment. A `char` array can be arbitrarily aligned, while pointers (such as something of type `char **`) have stricter alignment requirements. But `&str` wouldn't be an array; it would be a pointer type like `char **`. I did test `func((char**)&str)` with a simple assignment (writing `"def"` to the string) within `func`, and it indeed doesn't work, but I'm perplexed about the reason. In principle, `**&str` -> `*str` -> `*&str[0]` -> `str[0]`, so something about the cast `(char**)&str` is wrong that I can't put my finger on. – Lover of Structure May 26 '23 at 19:18
  • @LoverofStructure `(char**)&str` = `(char**)str` = `(char**)&str[0]`. The addresses are necessarily numerically identical. Suppose both the alignment requirement for inter-pointer casts & the strict aliasing rules were dropped. Then this casting is still semantically different from `&(char*){str}`. The latter stack-allocates a space for a pointer, loads the address of `str` into it, and takes the address of that pointer. The former wants to make pretend that there's a `char*` at the start of the array. But there are chars there and it's highly unlikely they together form a pointer to `str`. – Petr Skocik May 26 '23 at 20:16
  • @LoverofStructure `&str` is absolutely not an address of a pointer. It's the address of the array, which is numerically equal to the address of the 1st element, except that it's typed `element_type (*)[array_size]` rather than `element_type*` (so pointer arithmetic on it adds multiples of `sizeof(element_type[array_size])` rather than `sizeof(element_type)` ). As such, it is `char`-aligned (1-aligned) & therefore not suitably aligned for pointers (typically 4-aligned (32-bit systems) or 8-aligned (64-bit systems)), so the cast itself violates http://port70.net/~nsz/c/c11/n1570.html#6.3.2.3p7. – Petr Skocik May 26 '23 at 20:30
  • 1
    @PetrSkocik I think I got it. Feel free to have a look at my edit. – Lover of Structure May 26 '23 at 21:59
  • 1
    I had already upvoted anyway though I think with the `(char**)&str` thing you're getting into a bit of an unnecessary rabbit whole—if you want a usable `char**`, you create a valid `char*` and take its address (`&(char*){str}`), not try and reinterpret the first `sizeof(char*)` bytes of a random char array as a `char*`. ;-) – Petr Skocik May 27 '23 at 12:31