3
#include <stdio.h>

int main(void)
{
  char s[32];

example_1:
  scanf("%s", s);
  printf("%s\n", s);

example_2:
  scanf("%s", &s[0]);
  printf("%s\n", s);

example_3:
  scanf("%s", &s);
  printf("%s\n", s);
}
  1. Why does #3 work the same way as other 2?

  2. Is #3 valid at all? Why?

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
HighPredator
  • 790
  • 4
  • 21
  • 1
    See here: [How come an arrays address is equal to its value in C](http://stackoverflow.com/questions/2528318/how-come-an-arrays-address-is-equal-to-its-value-in-c) – M.M Mar 03 '15 at 23:12

2 Answers2

9
  1. Variant one depends on array decay, and is thus valid.

    (Arrays decay to pointers to their first element in most contexts, exceptions are: Address-of (&s), sizeof s, _Alignas(s) and _Alignof(s).)

  2. Variant two does what array-decay would do manually and more verbose, and is thus worse.

  3. Variant three is strictly Undefined Behavior, though it happens to work on most implementations.
    The sticking point is that &s is not of type char* after default promotions, though it points to the right address.

Seems to work is the most treacherous subset of UB.

Community
  • 1
  • 1
Deduplicator
  • 44,692
  • 7
  • 66
  • 118
2

While the types of the three are different :

  • s is a char[32] (an array of 32 chars), which transparently decays to a char* where needed
  • &s[0] is a char* (a pointer to char)
  • &s is a char(*)[32] (a pointer to an array of 32 chars)

they all resolve to the same value (the start address of the character array). So, when passing it to printf, which expects a char*, the result will be the same for all 3 (assuming the object representation of a char(*)[32] is the same as that of a char*, which is implementation dependent, but commonly the case).

Only the first two are valid though - the third just works by accident.

Sander De Dycker
  • 16,053
  • 1
  • 35
  • 40
  • 2
    Understood. So, just to be precise, is it correct to assume that case #3 is invalid because it violates *printf function format specification from the standard (in terms that if supplied argument doesn't correspond to format type it invokes UB straight away)? Or is here anyting else violated? – HighPredator Mar 03 '15 at 13:08
  • @HighPredator: Yes, that's it. – Deduplicator Mar 03 '15 at 13:10
  • @Deduplicator, thanks. I got one more question to follow this topic. Let's say we have the following: > char s[32]; > char dest[32]; > /* some code here */ > memcpy(&dst, &s, sizeof(s)); Is this wrong as well? P.S. sorry, I'm still struggling with the editor here... – HighPredator Mar 03 '15 at 13:19
  • @HighPredator: As `s` is of the proper size (It's a `char[32]`), that "works". It's unneccessarily error-prone, due to being unrelated though. – Deduplicator Mar 03 '15 at 13:24
  • @Deduplicator, could you please clarify the "unrelated" part a bit? Not sure I understand it correctly. – HighPredator Mar 03 '15 at 13:26
  • @HighPredator: In that snippet, there's no obvious reason why they all should be the same size, aside from they just are. – Deduplicator Mar 03 '15 at 13:30
  • @Deduplicator, what about the fact that we supply memcpy with arguments of type (char*)[32] instead of char*? Will it always resolve to a correct memory address or it can be classified as a logical error or smth? – HighPredator Mar 03 '15 at 13:35
  • @HighPredator: `memcpy` is not a variadic function. The prototype says the argument is of type `void*`, and thus the compiler silently converts (or complains if that's impossible, because you gave something which isn't a data-pointer, or is cv-qualified). – Deduplicator Mar 03 '15 at 13:37
  • @Deduplicator, memcpy, sorry. Made an edit. So just a logical errror, right? – HighPredator Mar 03 '15 at 13:38
  • @HighPredator: It's perfectly valid to take the address of the array to pass it to `memcpy`, it's just very unusual (I personally don't do it, but one could make a point this is more consistent with other `memcpy` calls; what usually does indicate a mistake, though, is passing the size of the source instead of the target). Same for the `printf` call: The value is correct, it's just the type mismatch: `scanf("%31s", (char *)&s);` would be well-defined (but ugly). – mafso Mar 03 '15 at 14:50
  • "when passing it to printf, which expects a char*, the result will be the same for all 3" - true on common hardware but not guaranteed . Arguments don't get converted to match the format specifier; they have to match it already. The argument of the wrong type may be using a different value representation even though the value is the same. – M.M Mar 03 '15 at 23:14
  • @MattMcNabb : while that's theoretically possible, I haven't seen a platform that uses a different object representation for the address of an array, and the address of the first item of that array. But I'll add a caveat. – Sander De Dycker Mar 04 '15 at 08:32