I am confused about how pointers to characters work. when I run the following code, what happens?
int main()
{
char* word;
scanf("%s",word);
printf("%s",word;
}
the first line in the main is defining a pointer to char without initialization. scanf should store the word somewhere and give the address to the pointer, right? what if I input a big string, would it overwrite something in the memory?
And what happens in the first line in the following code other than defining a pointer to char. Does the compiler set some limits? or I can't exceed the size specified, right? If done, I will have a run time error, right? what is the difference between the two cases?
int main()
{
char word[100];
scanf("%s",word);
printf("%s",word;
}
What about pointers to other types? Can I just keep writing to the following places using offsets?
scanf should store the word somewhere and give the address to the pointer, right?
No. It is the other way around. You define the address where scanf shall store the value. As you fail to initialize the pointer to some valid address, you cause undefined behaviour that might result in a crash in best case or seem to work in worst case.
And what happens in the first line in the following code other than defining a pointer to char.
There is no pointer involved at all. An array is not a pointer. An array provides all the memory it needs to store all its members. A pointer doesn't do this.
Does the compiler set some limits? or I can't exceed the size specified, right?
You can write wherever you want. No one will prevent you from doing this. At least no from trying. If you write to some location that does not belong to the memory you allocated, you again cause undefined behaviour.
The function scanf requires that you pass it the address of a sufficiently large memory buffer for storing the string. If you don't do this, then you will be invoking undefined behavior (i.e. your program may crash).
Simply passing a wild pointer (i.e. an arbitrary memory address) is not sufficient. Rather, you must reserve the memory that you intend to use, for example by declaring an array or by using the function malloc.
Using the %s scanf conversion format specifier by itself is not a good idea, because even if the allocated memory buffer has a size of 100 characters, if the user types more than 99 characters (100 including the terminating null character), then the function will write to the array out of bounds, causing undefined behavior. Therefore, you should always limit the number of characters that are written, in this case by writing %99s instead of simply %s.
Also, before using the result of scanf, you should always check the return value of the function, and only use the result if the function was successful.
int main()
{
char word[100];
if ( scanf( "%99s", word ) == 1 )
printf( "%s\n", word );
else
printf( "input error!\n" );
}
what if I input a big string, would it overwrite something in the memory?
It doesn't have to be a "big" string. Writing even a "small" string to a wild pointer will cause undefined behavior and something important may be overwritten, or your program may crash.
And what happens in the first line in the following code other than defining a pointer to char. Does the compiler set some limits?
The line
char word[100];
will allocate an array of 100 characters, i.e. it will give you a memory buffer that is sufficiently large to store 100 characters. This does not give you a pointer. However, when using the array word in the line
scanf("%s",word);
the array word will decay to a pointer to the first element.
Does the compiler set some limits? or I can't exceed the size specified, right?
The compiler won't prevent you from writing to the array out of bounds, but if you allow this to happen, then your program will have undefined behavior (i.e. your program may crash). Therefore, you probably don't want to allow that to happen.
If done, I will have a run time error, right?
If you are lucky, then yes, your program will crash immediately and you will easily be able to identify and fix the bug. If you are unlucky, then no, your program won't crash, but will work as intended, and you won't notice the bug for a very long time, until much later in development, when one day the bug starts overwriting something important in your program. In that case, the bug will probably be hard to diagnose.
This is because C is not a memory-safe language.
However, because these kinds of bugs are often hard to find, there are tools which can help detect these kinds of bugs, such as valgrind and AddressSanitizer.
According to the description of the conversion specifier %s in the C Standard
If no l length modifier is present, the corresponding argument shall
be a pointer to the initial element of a character array large enough
to accept the sequence and a terminating null character, which will be
added automatically.
That is when you pass a pointer as an argument of the function that corresponds to the format %s it shall point to the first element of a character array where the input string will be stored. The character array shall be large enough to accommodate the entered string (including the appended terminating zero character '\0')
In the first program
int main()
{
char* word;
scanf("%s",word);
printf("%s",word;
}
the pointer word is uninitialized and has an indeterminate value. So these two statements
scanf("%s",word);
printf("%s",word;
invoke undefined behavior.
You need to provide a valid value of the pointer that will point to a character array. For example
char s[100];
char *word = s;
Or you can allocate memory dynamically like
char *word = malloc( 100 * sizeof( char ) );
In the second program
int main()
{
char word[100];
scanf("%s",word);
printf("%s",word;
}
the array word used as an argument is implicitly converted to a pointer to its first element. If you will enter a string that fits in the array with 100 elements then the program will behave correctly.
However if you will enter 100 or more characters without embedded spaces then the program again will have undefined behavior.
To avoid such a situation you can specify the maximum length of the string that can be read in the array word by using the length modifier the following way
scanf("%99s",word);
If you want to input a string that may have embedded spaces you should use another conversion specifier. For example
scanf("%99[^\n]", word );
or
scanf(" %99[^\n]", word );
Here are two demonstration programs that show the difference between the two conversion specifiers used to enter a string.
#include <stdio.h>
int main(void)
{
char word[100];
scanf( "%99s", word );
puts( word );
return 0;
}
If to enter the string
Hello Mohammed Elbagoury
then the program output will be
Hello
And the second program
#include <stdio.h>
int main(void)
{
char word[100];
scanf( "%99[^\n]", word );
puts( word );
return 0;
}
Again if to enter
Hello Mohammed Elbagoury
then the program output will be
Hello Mohammed Elbagoury
If you will enter more than 99 characters then only the first 99 characters will be stored in the array appended with the terminating zero character '\0'.
As for your this question
Can I just keep writing to the following places using offsets?
then you can use the pointer arithmetic to store data in any position of an array. for example
int a[10];
scanf( "%d", a + 5 );
In this case a number will be written in the element of the array a[5].
The above statement is equivalent to
scanf( "%d", &a[5] );
Related
The goal is to count all the vowels from a char* the user puts in. The program has other functions and this is called from main.
I have also included stdio.h, stdbool.h, and string.h
char* countWord;
int vowels;
printf("Type the word to count vowels:");
scanf("%s", &countWord);
vowels = vowelCount(countWord);
printf("%d", vowels);
The following is the function I was used. I also tried strlen(string) which caused a crash as well.
int vowelCount(char* string){
int vowels;
int i;
int size;
printf("function entered");
for (; *string; string++){
if (string[i] == 'a'){
vowels++;
} else if(string[i] == 'e'){
vowels++;
} else if(string[i] == 'i'){
vowels++;
} else if(string[i] == 'o'){
vowels++;
} else if(string[i] == 'u'){
vowels++;
}
}
return vowels;
}
What am I doing wrong? I'm new to C but have experience in other languages.
Thanks in advance.
Exercising undefined behavior...
You have not allocated any space for the pointer to point at, so when you're trying to use it, the behavior is undefined.
Simply make some space for it:
char buffer[1000];
char* countWord = buffer;
and there's another mistake:
scanf("%s", &countWord);
^
You shouldn't use an address of (&) operator here. Just drop it. You're reading a string into the target of the pointer, not the pointer itself.
Also note that you're doing some mixed code in your function. You're using an uninitialized variable i, yet that seems unnecessary since you're incrementing the pointer string. So you want to drop i and change the if statement to
if (*string == 'a')
And be sure to initialize vowel as well:
int vowel = 0;
From the scanf man page, the format specifier %s:
"Matches a sequence of non-white-space characters; the next pointer must be a pointer to char, and the array must be large enough to accept all the sequence and the terminating NUL character."
The pointer your code provides matching the sole %s specifier, however, is the address of the variable declared as a pointer to char: &countWord is of type pointer to pointer to char. Thus, scanf writes (or attempts to write) the matched sequence to a location sized for a pointer to char, not necessarily the sequence length + null terminator. This may thus write into unallocated memory, which is undefined behavior (and oftentimes, a segfault). Simply removing the address-of operator will not suffice on its own to resolve the issue, either, because simply declaring a pointer to char does not allocate the space for the characters you presumably want said pointer to point to at some point.
What you must do to read a string using scanf as you have attempted to is to ensure that sufficient space is allocated to store the sequence you will read, then pass scanf a pointer to that space. These could be allocated statically:
char countWord[512]; // Assumes input sequence will consist of no more than 511 characters, since space is needed for the terminating NUL character
Or dynamically:
char* countWord = malloc(sizeof(char) * 512); // Same size as the above, so input still must be no more than 511 characters, but dynamically allocated so will need to be explicitly freed later to avoid leaking memory
Given the space is allocated and sufficient, you can then pass countWord (which defined in either of the manners shown above, is effectively a pointer to char, no address-of required*)
If the sequence your scanf call is reading as input is under some defined input restriction, you can pass scanf a pointer to sufficient allocated space for the maximum allowable input sequence size, guaranteeing the read sequence will not exceed the allocated space.
This, however, depends on the input following said limits. Better would be for your code to limit how much it might read, so it really is guaranteed that you won't access unallocated memory, even if your input source decides not to behave as expected. scanf provides a mechanism to do this by including a field width with the specifier, e.g.:
scanf("%511s", countWord); // Reads at most 511 bytes of input into the location pointed to by countWord, plus the NUL terminator.
Obviously, instead of 511 characters of input you would choose a suitable number that all valid inputs to the program should fall within.
The other answer noting that variables should be initialized before being read from is also true, but crashes specifically are more likely to result from interacting with unallocated memory than allocated but merely uninitialized memory (not that reading from the latter isn't undefined behavior).
*Technically there are some differences between a char array declared explicitly as an array and a pointer to char, but those differences are not particularly relevant to this question.
Your lack of memory allocation for countWord was already mentioned in other answers.
But you also have a problem with usage of uninitialized variable while counting the vowels.
You iterate using string pointer and add some random value i on top of that.
int vowelCount(char* string){
int i; // <<=== not initialized, holding ramdon garbage value
for (; *string; string++){
if (string[i] == 'a'){ // << adding random index to pointer.
...
how do i Initialize my code if all im using are words and no numbers?
I have been trying to just use char * but it is saying that its still not initialized
char *Carson;
printf("Enter a name:\n");
scanf("%s",Name);
printf("%s Hello Carson\n", Carson);
You either have to allocate memory dynamically and assign it to Carson (see e.g. `malloc? ), or make it an array. There's no way around it. And for that, the code must contain a number. The number could be input from the user though, so you won't have any actual numbers in the source.
Remember that in C all strings need an extra terminator character (added automatically by scanf) so remember to add space for it.
A solution without any number, I don't think this must be used for practical applications, just a hack
char Carson[sizeof(long long) * sizeof(long long)];
printf("Size = %d\n", sizeof Carson);
printf("Enter a name:\n");
scanf("%s",Carson);
printf("%s Hello Carson\n", Carson);
In my system it create a char array of 64 bytes = 8 * 8, the size of long long in most systems is 8 bytes although it's size depends on your compiler and operating system
you might like to initialize Carson like this:
char *Carson = malloc(sizeof(char)*200);/* for 200 characters */
Don't forget to add \0 terminator and also, donot forget to free it once you are done using it.
In order to initialize variables in C you need to use constants values, that is, expressions whose value can be known at compile time.
For integer or float types you can use mathematical formulas involving only constant operands, thus you can obtain still a constant value that can be used in a initiaiization.
What you call "words" have been called better "strings".
In C you are able to use strings that are constant at compile time, also called "string literals".
A string literal has to be indicated surrounded by quotes, like these examples:
"Hello world!"
"Peter & John"
"user#gmail.com"
and so on.
There are some rules that you need to remember: some special characters have "escape sequences" to be used inside a string literal.
Now you can use that string literals in order to initialize a char* variable:
char *name = "Mr. Smith";
char *city = "Amsterdam";
The result of the initialization gives a C string style, that is, an array of char object, whose length is the amount of quoted characters in the string literal, plus 1, because a null character is added at the end. Thus, in memory you have:
char *city ----> |A|m|s|t|e|r|d|a|m|\0|
Thus, city points to an array of 10 chars.
The last character, \0, means "null character", whose ASCII code is 0. Since it corresponds to a non-printable character, it has to be indicated with the escape sequence \0.
For more information, take a look on these websites:
Escape sequences in C
Storage of string literals
If you initialize a pointer to char object to a string literal, the compiler reserves memory automatically for you, son you don't need any malloc() at all.
However, you cannot modify the characters of such a string.
If you are interested in modify the characters, you can use better un array of char object:
char name[30] = "Schwarzenegger";
The array reserves 30 chars for the string literal "Schwarzenegger".
Only the first 14 are used for the string, plus 1 holding the null character attached at the end.
The rest of chars of the array have dummy information, but there is no problem because they are not printed. (The standard library functions always stop processing the string when they encounter a null character).
EDITED More information.
About your particular error message: "lack of initialization", the problem is that in the definition of the pointer to char object:
char *name;
you only have a "pointer to" an undefined block of memory.
You have to specify the array of char that name will be point to.
If you initialize with a string literal, there is not any problem, because the address of the string literal is passed to name.
But, since you are planning to use name for data input by means of scanf(), you have to allocate memory enough. You can do that other users have explained yet in their answers, that is, by using malloc().
I think there is need to do changes in your code,
char Carson = NULL;
Carson = (char)malloc(sizeof(char)*256);
printf("Enter a name:\n");
scanf("%s",Carson );
printf("%s Hello Carson\n", Carson);
in place of 256 u can use whatever value you want.
let me know if it works.
char *str;
printf("Enter string:\n");
scanf("%s",str);
OUTPUT:
runtime-check failure#3
str is being used without being initialized
Allocate an array and read into that:
char str[100];
if (scanf("%99s", str) != 1)
...error...
Or, if you need a pointer, then:
char data[100];
char *str = data;
if (scanf("%99s", str) != 1)
...error...
Note the use of a length to prevent buffer overflow. Note that the length specified to scanf() et al is one less than the total length (an oddity based on ancient precedent; most code includes the null byte in the specified length — see fgets(), for example).
Remember that %s will skip past leading white space and then stop on the first white space after some non-white space character. In particular, it will leave the newline in the input stream, ready for the next input operation to read. If you want the whole line of input, then you should probably use fgets() and sscanf() rather than raw scanf() — in fact, very often you should use fgets() and sscanf() rather than scanf() or fscanf(), if only because it make sensible error reporting a lot easier.
Its an undefined behavior if you dont initialize it.You have an uninitialized pointer which is reading data into memory location which may eventually cause trouble for you. You've declared str as a pointer, but you haven't given it a valid location to point to; it initially contains some random value that may or may not be a writable memory address.Try to allocate memory to the char *str;
char *str = malloc(sizeof(char)*100);
char *str declares str as a pointer to char type. scanf("%s",str) will read only E from the entered string.
You might NOT want to use exactly scanf("%s") (or get in the habit of using it) as it is vulnerable to buffer overflow (i.e. might accept more characters than your buffer can hold). For a discussion on this see Disadvantages of scanf
You have to allocate memory before assigning some value to a pointer. Always remember that pointer points to a memory location and you have to tell him what memory location you want to assign for him. So, for doing that you have to do :
str = (char*)malloc(sizeof(char));
This will assign you 1byte of memory block. So, you can assign only one character in this memory block. For a string you have to assign as many blocks depending on the number of characters in the string.Say, "Stack" requires 5 character and 1 extra block for '\0'. So, you have to assign at least 6 memory block to him.
str = (char*)malloc(6*sizeof(char));
I hope this will help you to grow more concepts in C.
Guys i have few queries in pointers. Kindly help to resolve them
char a[]="this is an array of characters"; // declaration type 1
char *b="this is an array of characters";// declaration type 2
question.1 : what is the difference between these 2 types of declaration ?
printf("%s",*b); // gives a segmentation fault
printf("%s",b); // displays the string
question.2 : i didn't get how is it working
char *d=malloc(sizeof(char)); // 1)
scanf("%s",d); // 2)
printf("%s",d);// 3)
question.3 how many bytes are being allocated to the pointer c?
when i try to input a string, it takes just a word and not the whole string. why so ?
char c=malloc(sizeof(char)); // 4)
scanf("%c",c); // 5)
printf("%c",c);// 6)
question.4 when i try to input a charcter why does it throw a segmentation fault?
Thanks in advance.. Waiting for your reply guys..
printf("%s",*b); // gives a segmentation fault
printf("%s",b); // displays the string
the %s expects a pointer to array of chars.
char *c=malloc(sizeof(char)); // you are allocating only 1 byte aka char, not array of char!
scanf("%s",c); // you need pass a pointer to array, not a pointer to char
printf("%s",c);// you are printing a array of chars, but you are sending a char
you need do this:
int sizeofstring = 200; // max size of buffer
char *c = malloc(sizeof(char))*sizeofstring; //almost equals to declare char c[200]
scanf("%s",c);
printf("%s",c);
question.3 how many bytes are being allocated to the pointer c? when i
try to input a string, it takes just a word and not the whole string.
why so ?
In your code, you only are allocating 1 byte because sizeof(char) = 1byte = 8bit, you need allocate sizeof(char)*N, were N is your "string" size.
char a[]="this is an array of characters"; // declaration type 1
char *b="this is an array of characters";// declaration type 2
Here you are declaring two variables, a and b, and initializing them. "this is an array of characters" is a string literal, which in C has type array of char. a has type array of char. In this specific case, the array does not get converted to a pointer, and a gets initialized with the array "this is an array of characters". b has type pointer to char, the array gets converted to a pointer, and b gets initialized with a pointer to the array "this is an array of characters".
printf("%s",*b); // gives a segmentation fault
printf("%s",b); // displays the string
In an expression, *b dereferences the pointer b, so it evaluates to the char pointed by b, i.e: T. This is not an address (which is what "%s" is expecting), so you get undefined behavior, most probably a crash (but don't try to do this on embedded systems, you could get mysterious behaviour and corrupted data, which is worse than a crash). In the second case, %s expects a pointer to a char, gets it, and can proceed to do its thing.
char *d=malloc(sizeof(char)); // 1)
scanf("%s",d); // 2)
printf("%s",d);// 3)
In C, sizeof returns the size in bytes of an object (= region of storage). In C, a char is defined to be the same as a byte, which has at least 8 bits, but can have more (but some standards put additional restrictions, e.g: POSIX requires 8-bit bytes, i.e: octets). So, you are allocating 1 byte. When you call scanf(), it writes in the memory pointed to by d without restraint, overwriting everything in sight. scanf() allows maximum field widths, so:
Allocate more memory, at least enough for what you want + 1 terminating ASCII NUL.
Tell scanf() to stop, e.g: scanf("%19s") for a maximum 19 characters (you'll need 20 bytes to store that, counting the terminating ASCII NUL).
And last (if markdown lets me):
char c=malloc(sizeof(char)); // 4)
scanf("%c",c); // 5)
printf("%c",c);// 6)
c is not a pointer, so you are trying to store an address where you shouldn't. In scanf, "%c" expects a pointer to char, which should point to an object (=region of storage) with enough space for the specified field width, 1 by default. Since c is not a pointer, the above may crash in some platforms (and cause worse things on others).
I see several problems in your code.
Question 1: The difference is:
a gets allocated in writable memory, the so-called data segment. Here you can read and write as much as you want. sizeof a is the length of the string plus 1, the so-called string terminator (just a null byte).
b, however, is just a pointer to a string which is located in the rodata. That means, in a data area which is read only. sizeof b is whatever is the pointer size on your system, maybe 4 or 8 on a PC or 2 on many embedded systems.
Question 2: The printf() format wants a pointer to a string. With *b, you dereferene the pointer you have and give it the first byte of data, which is a t (ASCII 84 or something like that). The callee, however, treats it as a pointer, dereferences it and BAM.
With b, however, everything goes fine, as it is exactly the right call.
Question 3: malloc(sizeof(char)) allocates exactly one byte. sizeof(char) is 1 by definition, so the call is effectively malloc(1). The input just takes a word because %s is defined that way.
Question 4:
char c=malloc(sizeof(char)); // 4)
shound give you a warning: malloc() returns a pointer which you try to put into a char. ITYM char *...
As you continue, you give that pointer to scanf(), which receives e.g. instead of 0x80043214 a mere 0x14, interprets it as a pointer and BAM again.
The correct way would be
char * c=malloc(1024);
scanf("%1024s", c);
printf("%s", c);
Why? Well, you want to read a string. 1 byte is too small, better allocate more.
In scanf() you should take care that you don't allow reading more than your buffer can hold - thus the limitation in the format specifier.
and on printing, you should use %s, because you want the whole string to be printed and not only the first character. (At least, I suppose so.)
Ad Q1: The first is an array of chars with a fixed pointer a pointing to it. sizeof(a) will return something like 20 (strlen(a)+1). Trying to assign something to a (like a = b) will fail, since a is fixed.
The second is a pointer pointing to an array of char and hence is the sizeof(b) usually 4 on 32-bit or 8 on 64-bit. Assigning something to b will work, since the pointer can take a new value.
Of course, *a or *b work on both.
Ad Q2: printf() with the %s argument takes a pointer to a char (those are the "strings" in C). Hence, printf("%s", *b) will crash, since the "pointer" used by printf() will contain the byte value of *b.
What you could do, is printf("%c", *b), but that would only print the first character.
Ad Q3: sizeof(char) is 1 (by definition), hence you allocate 1 byte. The scanf will most likely read more than one byte (remember that each string will be terminated by a null character occupying one char). Hence the scanf will trash memory, likely to cause memory sometime later on.
Ad 4: Maybe that's the trashed memory.
Both declaration are the same.
b point to the first byte so when you say *b it's the first character.
printf("%s", *b)
Will fail as %s accepts a pointer to a string.
char is one byte.
Why do 1, 2, and 3 work when 4 generates a segmentation fault? (See below.)
char c[10];
char* d;
1.
scanf("%s", &c);
printf("%s\n", &c);
2.
scanf("%s", c);
printf("%s\n", c);
3.
scanf("%s", &d);
printf("%s\n", &d);
4.
scanf("%s", d);
printf("%s\n", d);
Repeating the code in the question:
char c[10];
char* d;
1.
scanf("%s", &c);
printf("%s\n", &c);
This is likely to work as expected, but in fact the behavior is undefined.
scanf with a "%s" format requires an argument of type char*. &c is of type char (*)[10], i.e., it's a pointer to a char[10] array. It points to the same location in memory as the address of the 0th element of c, but it's of a different type. The same thing happens with the printf: the "%s" format tells it to expect a char* argument, but you're passing it a char(*)[10] argument.
Since scanf is a variadic function, there's no required type checking for arguments other than the format string. The compiler will (probably) happily pass the char (*)[10] value to scanf, assuming that it can handle it. And it probably can, on an implementation where all pointers have the same size, representation, and argument-passing mechanism. But, for example, a C compiler for an exotic architecture could easily make char* pointers bigger than pointers to larger types. Imagine a CPU whose native address points to, say, a 64-bit word; a char* pointer might be composed of a word pointer plus a byte offset.
2.
scanf("%s", c);
printf("%s\n", c);
This is better. c is an array, but in this context an array expression "decays" to a pointer to the array's first element -- which is exactly what scanf with a "%s" format requires. The same thing happens passing c to printf. (But there are still some problems; I'll get to that after the other examples.
3.
scanf("%s", &d);
printf("%s\n", &d);
Since d is a single char* argument, &d is of type char**, and again, you're passing arguments of the wrong type. If all pointers have the same representation (and the same argument-passing mechanism), and the input for the scanf is short enough, this might happen to "work". It treats the char* object as if it were an array of char. If char* is 4 bytes, and the input string is no more than 3 characters long, this will probably work -- as if you had used a char[4] and written the calls correctly. But it's extremely poor practice to store character strings directly into a pointer object, and there's a huge risk of writing past the end of the object, with unpredictable results. (Those unpredictable results include writing into memory that isn't being used for anything else, which could appear to work; such is the nature of undefined behavior.)
(The C standard gives special permission to treat any object as an array of characters, but in this case it's a very bad idea.)
4.
scanf("%s", d);
printf("%s\n", d);
Here the types are all correct, but unless you've initialized d to point to a sufficiently large array of char, it's likely to fail spectacularly (or, worse, appear to work "correctly", which means you've got a subtle bug that will probably show up later).
And now we get to what I mentioned above about other problems.
For example 4, I mentioned that d needs to point to a "sufficiently large" array. How large is "sufficiently large"? There's no answer to that. scanf("%s", ...) reads a whitespace-delimited sequence of characters with no upper bound on its length. If I run your program and hold down the x key, for example, I can provide an input string longer than any buffer you've provided, with unpredictable results (undefined behavior again).
The scanf function's "%s" format cannot be used safely (unless your program runs in an environment where you can control what will appear on the standard input stream).
One good way to read text input is to use fgets to read a line at a time, then use other functions to analyze the result. fgets requires you to specify the maximum length of the input; if the actual input exceeds the limit, it's truncated and left to be read by later calls. It's not quite as convenient as scanf, but it can be done safely. (And never use the gets function; like scanf("%s", ...), it cannot be used safely.)
Suggested reading:
Section 6 of the comp.lang.c FAQ does an excellent job of explaining C arrays and pointers, and how they're related (and not related). Section 12 discusses C standard I/O.
(I'm sorry this answer is so long; I didn't have time to make it shorter.)
You got undefined behavior in cases 3 and 4.
Cases one and two are the same, as both pointing to the first element in the array.
Case 3 is undefined, as you give a pointer to pointer to char when expecting pointer to char.
Case 4 is undefined, as the pointer d is not initialized.
3 works (on many platforms, and with a warning if you turn those on; technically it is undefined behavior) because you're abusing the pointer (treating &d, which is of type (char **), as (char *) and storing characters inside the memory intended for a pointer). 4 dies because the uninitialized pointer points to a random address.
The important question here is whether there is space in which to store the result.
scanf("%s", &c);
printf("%s\n", &c);
Is there storage? Yes, the address you take is that of the first element of the array. The array exists, so you can put the result there.
scanf("%s", c);
printf("%s\n", c);
Is there storage? Yes. Used like this, the array collapses into a pointer, which is passed same as above.
scanf("%s", &d);
printf("%s\n", &d);
Is there storage? Yes. It's not of the appropriate type, (char **, should be char *), but it shouldn't be any different than casting a char into a pointer type and storing it in a variable declared as a pointer. (Other answers say this is undefined behavior. I don't think it is, casting a char or any other integer type to a char * or other pointer type is well-defined, if ill-advised; show me where the standard says this is undefined.)
scanf("%s", d);
printf("%s\n", d);
Is there storage? Not that you've allocated. It could technically be the case that whatever happens to be in d points to a place in memory that won't segfault. Even if it does, it's not your memory and you could be overwriting something important, or it could change unexpectedly. You haven't told d where to find valid memory to point to, so you're playing pointer Russian roulette.