Speaking in terms of exact variable lengths and addresses:
Case 1:
char str1[] = "test";
allocates a 5 bytes memory space, say from 0x0050 to 0x0054, and str1 is the address of the first character of the string; that is, 0x0050 in this case.
When it is used in printf with a %s format specifier, the compiler knowing the str1 is an array of characters, directly sends the address of str1 to the printf function and it prints the string.
When it is used in printf with a %p format specifier, the compiler assumes that the coder is interested in the address of the string; again sends the address of str1 to the printf function and this time, it prints the address of the string.
Case 2:
char *str2 = "test";
allocates a 5 bytes memory space, say from 0x0050 to 0x0054. This time however, str2 is a pointer stored in another address, say 0x0030; and it's value is 0x0050, pointing the first character of the string.
When it is used in printf with a %s format specifier, the compiler knowing the str2 is a pointer to an array of characters, sends the value stored in str2 to the printf function; not the address of str2.
When it is used in printf with a %p format specifier, the compiler assumes that the coder is interested in the address of the string; again sends the value stored in str2 to the printf function.
Case 3:
char *arrStr[4];
arrStr[0] = str1;
Here, the behavior of the compiler resembles the Case 2 above.
When it is used in printf with a %s format specifier, the compiler knowing the arrStr[0] is a pointer to an array of characters, sends the value stored in arrStr[0] to the printf function (and in fact, this is the address of str1); not the address of arrStr[0].
When it is used in printf with a %p format specifier, the compiler assumes that the coder is interested in the address of the string; again sends the value stored in arrStr[0] to the printf function.
So, if you're interested in the address of the arrStr[0] (not
  the value that it is pointing to), you should use &arrStr[0] with a
  %p format specifier.