4

Some example code:

#include <stdio.h>

void func0(char *x)
{
  printf("func0, %s, %zu\n", x, sizeof(x));
}

//for comparison with func0
void func1(char **x)
{
  printf("func1, %s, %zu\n", *x, sizeof(*x));
}

int main()
{
  char x[10] = "hello";
  printf("%s, %zu, %zu, %zu\n", x, sizeof(x), sizeof(*x), sizeof("hello"));
  func0(x);
  func1(&x);
  return 0;
}

For func1() (for comparison with func0), there is a "warning: incompatible pointer types passing 'char (*)[10]' to parameter of type 'char **'" and "Segmentation fault" error.

But for func0(), isn't the type 'char [10]' converted to 'char *'? If so, how is printf("%s") still able to print out "hello"? As shown in the output of func0():

hello, 10, 1, 6
func0, hello, 8

Is the information of length (i.e. N of char[N]) passed to func0?

What is the difference between x in func0 and *x in func1? Aren't they both an address pointing to a char?

Thank you very much.


[EDIT] Thank you so much for the detailed answers!

Let me summarise the warning and error for func1().

char x[10] = "hello";

The type of &x is char (*)[10], which is a pointer to an array, not pointer to pointer (e.g. char **). Hence the warning.

x is already the address (pointing to the string), and you can only take the address of an actual memory space. &x is not the address of x. **x in func1(): trying to read from an illegal address (the value of "H" in this example?), hence the Segmentation fault.


The extra question is:

What is the use case for using &x (e.g. char (*) [N])?

I see the difference between x and &x, char * / char[N] vs. char (*) [N], but I cannot figure out a case when you have to use char( *) [N]. x and &x are the same address after all.

user7586189
  • 1,249
  • 8
  • 17
  • 2
    The char* strings in C are zero-terminated. This means the printf statement does not know the length of the strings, but simply prints every character up to the first zero character found. – Adder Aug 03 '18 at 13:33
  • @Adder, ah, that makes sense. Why does printf in func1 fail then? – user7586189 Aug 03 '18 at 13:35
  • 1
    `char **x` is a pointer that points to an array of pointers (actually your string). Using the value of that is `&(*x)` which is simply `x` -> `printf("func1, %s, %lu\n", x, sizeof(x));` – Hearner Aug 03 '18 at 13:37
  • @Hearner: `char **x` is not a pointer that points to an array of pointers. It is a pointer to a pointer to `char`. In order words, its type does not include any sort of an array. There might be an array of pointers at the pointed-to location, but that is not part of the type of `char **x`. – Eric Postpischil Aug 03 '18 at 13:43
  • [`sizeof` must be printed using `%zu`](https://stackoverflow.com/q/940087/995714) – phuclv Aug 03 '18 at 15:01
  • @phuclv or `"%zx"`, `"%zX"`, `"%#zX"`, `"%zo"`, or if do not mind an extra `o` you can go to the [`"%zoo"`](http://1.bp.blogspot.com/-S59r62hKMqU/UijTZRSbPpI/AAAAAAAAA-0/W8kLFh_-Xiw/s1600/zsl-london-zoo.jpg). ;-) – chux - Reinstate Monica Aug 03 '18 at 16:26

3 Answers3

4

there is a "warning: incompatible pointer types passing char (*)[10] to parameter of type char **" and "Segmentation fault" error.

That's right, &x expression produces a pointer to array of ten characters, hence the warning. If you want to pass a pointer to pointer, make a pointer, and take a pointer of it:

char *px = x; // alternatively you could write &x[0]
func1(&px);

But for func0(), isn't the type char [10] converted to char *?

That's right, character array "decays" to character pointer when you make a function call.

how is printf("%s") still able to print out "hello"

This is possible because x contains a null-terminated sequence of characters. Therefore, printf does not need to know the size of the array when processing %s: stopping at '\0' is sufficient.

Essentially, %s does not need to know the size of the array, it needs to know the length of the string, in the same way strlen "knows" it:

printf("func0, %s, %lu\n", x, strlen(x));
// Prints hello 5

I see the difference between x (char / char[N]) and &x, but I cannot figure out a case when you have to use char(*) [N]

Here is a small example:

void print_many(char (*rows)[10], size_t count) {
    for (size_t i = 0 ; i != count ; i++) {
        printf("%zu: %s\n", i+1, rows[i]);
    }
}

you call it like this:

char rows[10][] = {"quick", "brown", "fox"};
print_many(rows, 3);

Demo.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
1

Is the information of length (i.e. N of char[N]) passed to func0?

No. The parameter for func0 is char *. This is a pointer to char, nothing else. There is no length associated with it.

But for func0(), isn't the type 'char [10]' converted to 'char *'?

Yes. When x, which has type char [10], is used as a function argument, it is automatically converted to a pointer to its first argument. So the function receives a pointer to char.

If so, how is printf("%s") still able to print out "hello"?

The pointer passed to printf must point to the first character of a string whose end is marked by a null character. Thus, the pointer it receives points to a char containing 'h', after which there is a char containing 'e', then 'l', then 'l', then 'o', then zero. printf prints characters one by one until it sees the zero.

What is the difference between x in func0 and *x in func1?

If func1 were passed a pointer to a pointer to char, then using *x to pass a pointer to char to printf could be okay. However, your call to func1 is func1(&x). This is not passing a pointer to a pointer to a char. Since x is an array of 10 char, &x is a pointer to an array of 10 char. It is not a pointer to a pointer.

An array is not a pointer. Although arrays are often automatically converted to pointers, this automatic conversion does not happen when the array is the operand of unary &. (It also does not happen when the array is the operand of sizeof or _Alignof, or when the array is a string literal used to initialize an array.)

To produce a pointer to a pointer to char from the array x, you would have to take the address of a pointer to its first character. There is no way to do this in a simple expression, because x is the array, not a pointer. You could create a pointer to its first character with char *y = x;, and then you could pass &y to func1.

Note

Do not print sizes from sizeof using %lu. Use %zu. The z modifier is specifically for the size type, size_t.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
0

For the question in the title, print("%s\n", x) does not care if x is a pointer or an array, as long as it contains a zero-terminated string.

Next, only the topmost dimension of an array can be transparently coerced to a pointer, that's what the warning about.

Finally, &x points to the zero elements of your array, so when you dereference it in func1, array's first few elements (might be even eight of them, so two the rightmost are not even initialized) are reinterpreted as a pointer-to char which almost certainly points to nowhere. That's how the segfault arised.

bipll
  • 11,747
  • 1
  • 18
  • 32