4

This simple code is what I would expect:

#include <stdio.h>

int main()
{
    char input[20];
    printf("enter a string: ");
    scanf("%[^\n]s", input);
    printf("input: %s\n", input);
}

where input is (char*)[20]. But then why can I pass this:

#include <stdio.h>

int main()
{
    char input[20];
    printf("enter a string: ");
    scanf("%[^\n]s", &input);
    printf("input: %s\n", input);
}

and it still compiles and runs? The argument &input passed should be (char**)[20] which should not be correct, yet it runs. Why?

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
milanHrabos
  • 2,010
  • 3
  • 11
  • 45
  • scanf doesn't perform compile time type checking, you could pass anything you want and it would compile. Your compiler will warn you about this though with the right flags. – Peter Dec 21 '20 at 12:13
  • 1
    It's `char(*)[20]`, not `(char**)[20]`. But solid question otherwise, assuming that small difference doesn't make the question moot for you. – StoryTeller - Unslander Monica Dec 21 '20 at 12:14
  • @Peter ok, but it does not only compile, it even runs. So it has no problem in runtime – milanHrabos Dec 21 '20 at 12:15
  • 1
    @milanHrabos That can also be explained: https://stackoverflow.com/questions/2528318/how-come-an-arrays-address-is-equal-to-its-value-in-c – Peter Dec 21 '20 at 12:18
  • @StoryTeller-UnslanderMonica ok, so to clarify (for myself), the `input` is type `(char*)` and `&input` is `char(*)[20]`? Because I thought the second one as double address since the array is already address, and you take an address of it – milanHrabos Dec 21 '20 at 12:19
  • Arrays and pointers aren't the same. – Bonny4 Dec 21 '20 at 12:19
  • 2
    @milanHrabos - Arrays are not pointers. They are an aggregate object type, and don't contain an address as a value. When you deal with arrays, they *usually* get converted to a pointer unto their first element (cheaper to pass around normally, so a design choice). But, when you take their address, you don't get a double pointer, you get a pointer to the array (which is why the type is composed of `char[20]`, the size information is part of the array type). – StoryTeller - Unslander Monica Dec 21 '20 at 12:23
  • @milanHrabos Another way to look at it: pointers are variables that *hold* an address. Arrays are chunks of memory that *have* an address and arrays are passed to functions by passing that address, which is why they look like pointers in function calls. – Andrew Henle Dec 21 '20 at 12:27
  • @milanHrabos Why did you delete the question? after I have posted an answer? – dreamcrash Dec 25 '20 at 19:17

1 Answers1

8

In your code, the expression input (when used as an argument to scanf) will evaluate (i.e. decay) to the address of the first element of the 20-character array and the expression &input will evaluate to the address of the array itself - which will be the same, in this case. This can be demonstrated by adding a line like the following to your code:

    printf("%p %p\n", (void*)(input), (void*)(&input)); // Print the two addresses - SAME!

Thus, your call to scanf will actually pass the correct value (the address of the input buffer).

However, a good compiler will warn you about the incompatible pointer type; for example, clang-cl generates this:

warning : format specifies type 'char *' but the argument has type
'char (*)[20]' [-Wformat]

It is, of course, up to you whether you want to address the warning or ignore it; but, in more complex code, such 'mistakes' can cause run-time errors that are very difficult to track down.

anatolyg
  • 26,506
  • 9
  • 60
  • 134
Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • And how does `scanf` function work? Because if it takes my `&input` of type `char(*)[20]`, and the scanf will put another char by `input + 1`, then the arithmetic is wrong, because it won't move one character further, but 20 characters further. So still, the two different argument (input, &input), depict different types. So the scanf has to handle it somehow. But how? Does it cast it to (char*) type anyway? – milanHrabos Dec 21 '20 at 12:39
  • @milanHrabos Internally, the `scanf` function just recieves arguments it takes as addresses; it determines the types of those pointers according to the corresponding format specifiers - in your case, a `%s` tells it it's a `char*` type (or *should* be). The C language allows you to *lie* to a function call; but, just because you can, doesn't mean that you should. – Adrian Mole Dec 21 '20 at 12:42
  • Ok, so as I said, it will treat as `char*` anyway. No meter if I pass `char(*)[20]`, it still treats it as `char*` becuase of `%s` in the first argument right? – milanHrabos Dec 21 '20 at 12:44
  • 1
    Pretty much, yeah. In your case, there's no problem. However, if you pass an `int*` argument when the format specifier expects a `double*` (for example), then the `scanf` *could* cause memory corruption. – Adrian Mole Dec 21 '20 at 12:45
  • @milanHrabos "No meter if I pass `char(*)[20]`, it still treats it as `char*` becuase of `%s` in the first argument right?" No. Well your system and (almost) any other real world system this statement is true, but not according to the c standard. A machine can use 2 different ways to store a pointer `char *` and `char (*)[20]`, in which case it would cause UB. – 12431234123412341234123 Dec 21 '20 at 13:47