First, I read that:
array
&array
&array[0]
will all be the same as long as \"ar
Seems that
&ar
is taken as a pointer to the first element instead of a pointer to "ar", which itself is a pointer to the pointer of the first element if I am not mistaken.
You are mistaken. &ar
is a pointer to the array ar
, but the array ar
is not a pointer of any sort (it's an array), so &ar
is not a pointer to a pointer.
An array is a contiguous sequence of objects - in the case of ar
, it's a contiguous set of 4 char
s. &ar
is a pointer to this set of 4 chars, which necessarily means it points at the same place as &ar[0]
, a pointer to the first char
in that set. It has a different type, though: &ar
has type char (*)[4]
which means "pointer to array of 4 chars" and &ar[0]
has type char *
, which means "pointer to char".
The confusion arises because in almost all expressions, ar
evaluates to a pointer to the first element of the array (the exceptions to this are when it's the operand of the unary &
operator or the sizeof
operator). This doesn't mean that ar
is a pointer though - it's not - just that in most cases it evaluates to a pointer value.
If your code compiled without warnings, you are not making full use of your compiler. It will help you if you ask it to do so. Learn how to make it compile with warnings, and learn how to fix the problems it diagnoses. I compiled the code below with one or the other of these commands:
gcc -O3 -g -std=c99 -Wall -Wextra -m64 array-stuff.c -o array-stuff
gcc -O3 -g -std=c99 -Wall -Wextra -m32 array-stuff.c -o array-stuff
That's a good starting point for clean code with GCC. Indeed, -Wall
without -Wextra
is pretty good too.
Here's an adaptation of your code (in a file array-stuff.c
) — although most of it is different:
#include <stdio.h>
#include <inttypes.h>
int main(void)
{
// DC4M = Doesn't compile for me, because I compile with stringent warnings
char ar[16] = { 'a', 'b', 'c', '\0' }; // Note explicit size
printf("%-18s %s\n", "Code:", "char ar[16] = { 'a', 'b', 'c', '\0' };");
//printf("argument: &ar %s\n", &ar); // DC4M
printf("argument: &ar[1] %s\n", &ar[1]);
printf("%-18s 0x%" PRIXPTR "\n", "ar:", (uintptr_t)ar);
printf("%-18s 0x%" PRIXPTR "\n", "&ar:", (uintptr_t)&ar);
printf("%-18s 0x%" PRIXPTR "\n", "(ar+1):", (uintptr_t)(ar+1));
printf("%-18s 0x%" PRIXPTR "\n", "(&ar+1):", (uintptr_t)(&ar+1));
printf("%-18s 0x%" PRIXPTR "\n", "&ar[1]:", (uintptr_t)(&ar[1]));
printf("%-18s 0x%" PRIXPTR "\n", "&(ar[1]):", (uintptr_t)(&(ar[1])));
printf("%-18s 0x%" PRIXPTR "\n", "(&ar)[1]:", (uintptr_t)((&ar)[1]));
printf("%-18s %zu\n", "sizeof(ar):", sizeof(ar));
printf("%-18s %zu\n", "sizeof(&ar):", sizeof(&ar));
printf("%-18s %zu\n", "sizeof(void*):", sizeof(void*));
printf("%-18s %zu\n", "sizeof(ar[1]):", sizeof(ar[1]));
printf("%-18s %zu\n", "sizeof(&ar[1]):", sizeof(&ar[1]));
printf("%-18s %zu\n", "sizeof(&(ar[1])):", sizeof(&(ar[1])));
printf("%-18s %zu\n", "sizeof((&ar)[1]):", sizeof((&ar)[1]));
{
char a = 's';
char *pa = &a;
printf("%-18s %s\n", "Code:", "char a = 's';");
printf("%-18s %s\n", "Code:", "char *pa = &a;");
//printf("argument: &pa %c\n", &pa); // DC4M
printf("%-18s 0x%" PRIXPTR "\n", "&pa:", (uintptr_t)&pa);
printf("%-18s 0x%" PRIXPTR "\n", "&a:", (uintptr_t)&a);
printf("%-18s 0x%" PRIXPTR "\n", "pa:", (uintptr_t)pa);
}
{
char *pa = &ar[0];
char **ppa = &pa;
//printf("argument: &pa %s\n", ppa); // DC4M
printf("%-18s %s\n", "Code:", "char *pa = &ar[0];");
printf("%-18s %s\n", "Code:", "char **ppa = &pa;");
printf("%-18s 0x%" PRIXPTR "\n", "&pa:", (uintptr_t)&pa);
printf("%-18s 0x%" PRIXPTR "\n", "ppa:", (uintptr_t)ppa);
printf("%-18s 0x%" PRIXPTR "\n", "*ppa:", (uintptr_t)*ppa);
printf("%-18s 0x%" PRIXPTR "\n", "&ppa:", (uintptr_t)&ppa);
}
}
This is the output from a Mac OS X 10.7.4 machine with a 64-bit compilation:
Code: char ar[16] = { 'a', 'b', 'c', '
argument: &ar[1] bc
ar: 0x7FFF6C9DE570
&ar: 0x7FFF6C9DE570
(ar+1): 0x7FFF6C9DE571
(&ar+1): 0x7FFF6C9DE580
&ar[1]: 0x7FFF6C9DE571
&(ar[1]): 0x7FFF6C9DE571
(&ar)[1]: 0x7FFF6C9DE580
sizeof(ar): 16
sizeof(&ar): 8
sizeof(void*): 8
sizeof(ar[1]): 1
sizeof(&ar[1]): 8
sizeof(&(ar[1])): 8
sizeof((&ar)[1]): 16
Code: char a = 's';
Code: char *pa = &a;
&pa: 0x7FFF6C9DE560
&a: 0x7FFF6C9DE56F
pa: 0x7FFF6C9DE56F
Code: char *pa = &ar[0];
Code: char **ppa = &pa;
&pa: 0x7FFF6C9DE558
ppa: 0x7FFF6C9DE558
*ppa: 0x7FFF6C9DE570
&ppa: 0x7FFF6C9DE550
And this is the output from a 32-bit compilation:
Code: char ar[16] = { 'a', 'b', 'c', '
argument: &ar[1] bc
ar: 0xC008A670
&ar: 0xC008A670
(ar+1): 0xC008A671
(&ar+1): 0xC008A680
&ar[1]: 0xC008A671
&(ar[1]): 0xC008A671
(&ar)[1]: 0xC008A680
sizeof(ar): 16
sizeof(&ar): 4
sizeof(void*): 4
sizeof(ar[1]): 1
sizeof(&ar[1]): 4
sizeof(&(ar[1])): 4
sizeof((&ar)[1]): 16
Code: char a = 's';
Code: char *pa = &a;
&pa: 0xC008A668
&a: 0xC008A66F
pa: 0xC008A66F
Code: char *pa = &ar[0];
Code: char **ppa = &pa;
&pa: 0xC008A664
ppa: 0xC008A664
*ppa: 0xC008A670
&ppa: 0xC008A660
When you understand how the various numbers were arrived at, you will be well on your way to understanding things.
Note that &array[1]
is interpreted as &(array[1])
; it is different from (&array)[1]
in type and size. The postfix operators such as array subscripting bind more tightly than the unary operators such as the address (&
) and indirection (*
) operators.
The printf format %s means “The corresponding argument is a pointer to char. Print the string at that location.” And a string, for this purpose, is a sequence of characters ending in a null character.
When you passed &ar
to printf, you passed the address of the 'a' (although the type is wrong; printf expects a pointer-to-char, and you passed a pointer-to-array-of-char, but they have the same address), and printf saw the string 'a', 'b', 'c', '\0', so it printed "abc". The same would have happened if you passed ar
or &ar[0]
; those evaluate to the same address.
When you passed &ar[1]
to printf, you passed a pointer to where the 'b' is, and printf saw the string 'b', 'c', '\0', so it printed "bc".
If you want to pass just the single character at a location, use the %c format and pass a character (instead of a pointer to character). For example, if you use the %c format with *ar
, 'a' will be printed, and, if you use %c with *&ar[1]
, 'b' will be printed.
Seems that &ar is taken as a pointer to the first element instead of a pointer to "ar", which itself is a pointer to the first element if I am not mistaken.
When used in an expression, ar
acts as a pointer to the first element of the array, the same as &ar[0]
. &ar
and ar
are the same address (the first character in the array is at the same address as the start of the array) although they have different types (pointer to array of char and pointer to char).
the output for %c isn't even a character
It is a character, just not what you were expecting and perhaps not a normal character or a printable character. %c expect to be passed a character argument, but you passed it an address argument.
If so, how does the compiler verify that the identifier following "&" is in reference to an array, does it actually search for it in a list of arrays so for declared?
The parsing is more complicated than that (essentially, the identifier is identified as a known array before the & is considered, then the combined expression of the & and the identifier is evaluated). However, the effect is that &ar
evaluates to the same address as the first element.