What you have here is a classic example of code that seems to work, but for the wrong reasons.
Let's review a few things about printf
and scanf
. The format specifier %d
is for values of type int
. You can read an integer like this:
int i;
scanf("%d", &i);
And you can print it back out like this:
printf("%d
", i);
Why does one use an &
and one does not? Well, C uses what's called "pass by value". If we wrote
scanf("%d", i); /* WRONG */
we would be passing the value of i
to scanf
. But we don't want to pass the (old) value of i
to scanf
, we want scanf
to read a new value, and store it into i
. In other words, we want scanf
to, in effect, pass the new value of i
back to us. For that to work, we instead pass scanf
a pointer to the variable i
where we want it to store the just-read integer. That's what the &
does -- it generates a pointer to i
.
When we call printf
, on the other hand, the regular way of passing arguments works just fine. We do want to pass i
's value to printf
so that it can print it out. If we were to call
printf("%d
", &i); /* WRONG */
it wouldn't work, because printf
expects an int
, and here we're wrongly handing it a pointer-to-int
.
So now we've learned that for integers with %d
, printf
wants an int
and scanf
wants a pointer-to-int
.
Let's talk about characters. The format %c
is for characters. We can read one character with scanf
:
char c;
scanf("%c", &c);
And we can print it with printf
:
printf("%c
", c);
Again, the pattern is exactly the same. scanf
needs a pointer, so that it can fill in the value, so we pass &c
. But printf
just needs the value, so we pass plain c
.
Now we get to strings. A string in C is an array of characters. Also strings in C are always terminated by a special null character, ''
, that marks the end of the string. So if we wanted to declare a variable that could contain strings up to 9 characters long, we might write
char s[10];
That gives us room for 9 characters, plus the terminating ''
.
But arrays are special in C: Whenever you pass an array to a function, or whenever you do anything that would require the "value" of the array, what you get instead (what the compiler automatically generates for you) is a pointer to the array's first element.
What this means is that to read a string with scanf
and %s
, we can just call:
scanf("%s", s);
"But where is the &
?", you ask. "I thought you always needed an &
when calling scanf
!"
Well, not quite. You always need a pointer when calling scanf
. And in fact, when you called scanf("%s", s)
, it was just as if you had written
scanf("%s", &s[0]);
When you use %s
with scanf
, it expects a pointer to the first of several characters, that is, a pointer to the beginning of an array of characters, where it should begin writing the string it reads. (How does it know how big the array is? What if the user types a string that's too long to fit in the array? We'll get to those points in a moment.)
You can print strings with %s
too, of course, and it looks like this:
printf("%s
", s);
This is, again, just as if you had written
printf("%s
", &s[0]);
When you use %s
with printf
, it expects a pointer to the first of several characters which it should begin printing, until it finds the terminating ''
character.
So %s
is special with printf
and scanf
, because strings are special (because arrays are special). With %d
and %c
and just about every other format specifier, you usually need a &
when you call scanf
, and you usually don't want that &
when you call printf
. But with %s
, you usually don't want the &
for either printf
or scanf
.
(And if we think about it a bit more carefully, the exception is not so much that scanf
and %s
does not need the &
. Remember, the rule is really, scanf
always needs pointers. The only reason scanf
and %s
doesn't need an &
is that when you pass an array, you get a pointer to the array's first element automatically. So the exception is really for printf
and %s
: printf
and %s
does expect a pointer, and the reason printf
and %s
is designed to expect a pointer is that there's no way to not give it one: it has to accept a pointer, because for strings, that's what you always end up giving it.)
So the rule with %s
is that scanf
expects a pointer to the first of several characters, and printf
expects a pointer to the first of several characters, too.
So now, with all that background out of the way, we can look at your code. You basically wrote
char c;
scanf("%s", &c);
At first this might seem to be kinda, sorta, almost correct. scanf
and %s
wants a pointer to a character, and you gave it &c
, which is a pointer to a character. But %s
really wants a pointer to the first of several characters. But you gave it a pointer to just a single character. So when the user types a string, the first character typed will get stored in c
, but the rest of the characters, and the terminating ''
, will get written to unallocated memory somewhere off to the right of variable c
. They'll overwrite ("clobber") memory that was, perhaps, used for something else. This is a serious problem, but it might not become evident right away.
Finally, you tried to print things out again with printf
. You first tried
printf("%s
", c); /* WRONG */
but this didn't work at all. The reason is that %s
with printf
expects a pointer-to-char
, but you gave it a plain char
. Suppose c
contains the letter 'A'
. This would end up asking printf
to go to address 65 and begin printing characters until it finds the terminating ''
. Why address 65? Because 65 is the ASCII code for A
. But there's probably not a proper, null-terminated string starting at address 65 in memory; in fact there's a good chance your program doesn't have permission to read from address 65 at all.
So then you tried
printf("%s
", &c); /* ALSO WRONG */
and this seemed to work. It "worked" because, if scanf succeeded in storing a complete string into c
and the unallocated memory off to the right of it, and if clobbering that memory somehow didn't cause (too many) other problems, then when you pass the pointer &c
to printf
, printf can find those characters, making up a string, and print them out.
So it "works", but as I said, for the wrong reasons: in the process it stomps all over memory it doesn't "own", and sooner or later, something else is going to not work as a result.
How should you have scanned and printed a string? One way is like this, as we saw before:
char s[10];
scanf("%s", s);
printf("%s
", s);
Now when scanf
gets a pointer to the first element of the array s
, it has 10 characters to play with.
We really do have to worry about the possibility that the user will type more than 9 characters. But there's a fix for that: we can tell scanf
how long a string it's allowed to read, how many characters it's allowed to write to the array we handed it:
scanf("%9s", s);
That 9
in there tells scanf
that it's not allowed to read more than 9 characters from the user. And since 9 is less than 10, there's still room for the terminating ''
character.
There's much more that could be said about scanf
. As chqrlie noted in a comment, it's important to check its return value, to make sure it succeeded in converting as many values as you wanted it to. It's got some strange rules about whitespace. Unless you know what you're doing, you can't intermix calls to scanf
with calls to other input-reading functions like getchar
or fgets
-- you'll get strange results. And, finally, scanf is so persnickety and (in the end) so lacking in truly useful functionality that it's not really worth using at all. But those are topics for another day, since this answer is tl;dr already.