#c
#include "stdio.h"
int main(void)
{
int a[3][2] = {{10,2}, {2,3}, {3,4}};
printf("%p\n", a[0]);
printf("%p\n", (int *)a);
printf("%p\n", *a);
printf("%p\n", a);
printf("%p\n", &(a[0][0]));
return 0;
}
Код выдает одно и то же число...
Особенно поражает то, что равны a и *a.
Что происходит?
И еще когда смотрю на *(int *)a, то получаю 10...
Ответы
Ответ 1
Вас не должно удивлять равенство a и *a в данном контексте. Нечего удивительного
в этом нет. В языке С "двумерный массив" - это просто обычный одномерный массив, элементами
которого тоже являются одномерные массивы.
Одномерный массив, скажем, double d[10] - это просто плоский непрерывный компактный
блок памяти, размера 10 * sizeof(double), состоящий из десяти плотно лежащих друг за
другом элементов типа double. При этом адрес всего массива &d численно совпадает с
адресом его нулевого элемента &d[0], ибо "начинаются" они в одной и той же точке памяти.
Ситуация полностью аналогична равенству указателей вот в таком вот примере
struct S { int a; } s;
printf("%p %p\n", (void *) &s, (void *) &s.a);
// Оба указателя совпадают численно
Адрес всего объекта struct-типа численно совпадает с адресом самого первого поля
внутри этого объекта. Совершенно аналогичным образом адрес всего массива численно совпадает
с адресом самого первого (нулевого) элемента этого массива.
При этом в языке С выражение d типа "массив double [10]" в большинстве контекстов
(не во всех) неявно приводится к типу double * - указателю, указывающему на нулевой
элемент массива. Т.е. в большинстве контекстов выражение d ведет себя эквивалентно
выражению &d[0], т.е. массив внешне ведет себя как указатель. Это явление называют
array type decay. (Именно по этой причине массивы в С часто путают с указателями, хотя
на самом деле это разные типы и никаких указателей в массивах нет.)
Все это немедленно применимо и к двумерным массивам (и многомерным массивам), ибо,
как сказано выше, в языке С "двумерный массив" - это рекурсивная по своему устройству
структура: это просто одномерный массив, элементами которого являются одномерные массивы.
Вышеупомянутое явление array type decay работает неизменным образом на любом уровне
этой рекурсии.
Выше выражение a изначально имеет тип int [3][2], но в данном контексте неявно приводится
к типу "указатель на нулевой элемент массива a". Этот указатель имеет тип int (*)[2]
и указывает на подмассив a[0] типа int [2] в составе a.
Выражение *a изначально имеет тип int [2] - это подмассив a[0] в составе a, но в
данном контексте *a неявно приводится к типу "указатель на нулевой элемент массива
a[0]". Это указатель типа int *, который указывает на самый первый int в составе подмассива
a[0], т.е. на a[0][0].
И сам a, и a[0], и a[0][0] начинаются в одной и той же точке памяти, по каковой причине
числовые представления этих указателей совпадают
printf("%p %p %p\n", (void *) &a, (void *) a, (void *) *a);
// Все три указателя совпадают численно
Ответ 2
Возьмем тип int равный четырем байтам
Давайте представим массив в памяти:
+------------+----------+----------+----------+----------+
| Переменная | a[0][0] | a[0][1] | a[1][0] | a[1][1] |
+------------+----------+----------+----------+----------+
| Адрес | 0x61fe98 | 0x61fe9c | 0x61fea0 | 0x61fea4 |
+------------+----------+----------+----------+----------+
| Значение | 10 | 2 | 2 | 3 |
+------------+----------+----------+----------+----------+
В отладчике можно увидеть:
print &a => (int (*)[3][2]) 0x61fe98 // указатель на двумерный массив
print a => {{10, 2}, {2, 3}, {3, 4}}
print &(*a) => (int (*)[2]) 0x61fe98 // указатель на одномерный массив
print *a => {10, 2}
print &(**a) => (int *) 0x61fe98 // указатель на целое число
print **a => 10
Как видим, адреса двумерного массива a, одномерного подмассива a[0] ({10, 2}) и первого
элемента подмассива a[0][0] (10) равны. То есть:
&a == &(*a) == &(**a)
^ ^ ^
| | | a[0][0] (первое значение первого подмассива двумерного массива)
| |
| | a[0][] (первый подмассив двумерного массива)
|
| a[][] (весь двумерный массив)
Итак:
printf("%p\n", a[0]); // 1. адрес первого подмассива двумерного массива
(int (*)[2]) 0x61fe98
printf("%p\n", (int *)a); // 2. адрес двумерного массива, просто
// приведенный к указателю, имеет тип (int*),
// если разыменовать его получим 10
printf("%p\n", *a); // 3. то же самое, что и 1
printf("%p\n", a); // 4. не то же самое, что и 2! Имеет тот же
// адрес, но тип (int (*)[3][2])
printf("%p\n", &(a[0][0])); // 5. Указатель на первый элемент подмассива
// двумерного массива, то же самое что и 2
Ответ 3
printf("%p\n", a[0]); // <- 1
printf("%p\n", (int *)a); // <- 2
printf("%p\n", *a); // <- 3
printf("%p\n", a); // <- 4
printf("%p\n", &(a[0][0])); // <- 5
Для того, чтобы понять, что происходит полезно понимать, что такое на самом деле
массив. В С/C++ массив это обычный указатель на начало массива. Значения в массиве
хранятся последовательно. Также на указателях есть арифметика. То есть если прибавить
к указателю 1, то получим уаказатель на следующий элемент данного типа. Итак что же
такое запись a[i] на самом деле эта запись эквивалентна следующей: *(a+i) то есть берём
значение, которое хранится в i-ой ячейке после той, на которую указывает a. И на самом
деле такая запись тоже допустима: i[a] (попробуйте, например вывести ещё 0[a]). Итак
a[0] = *(a+0) = *a.
Таким образом равенство 1 и 3 установили.
Теперь разберём, почему a и *a совпадают. На самом деле потому, что они указывают
ровно в одно и тоже место. В памяти эти 6 чисел будут храниться последовательно: {10,
2, 2, 3, 3, 4}. Таким образом a указывающее на весь этот массив и a[0] указывающее
на кусок {10, 2} будут указывать в одно и тоже место, так как их начала совпадают.
Также a будет иметь тип int (*)[2]. Таким образом несмотря на то, что a и *a совпадают,
но a+1 и *a+1 совпадать уже не будут, так как указывают на типы разных размеров. И
сдвиг на 1 для этих указателей будет отличаться.
Теперь про оставшееся: a -- указатель, поэтому при конвертиации в указатель на int
((int *)a) указывать он продолжит на туже ячейку. И последнее: &(a[0][0]) = &(*(a[0]+0))
= a[0]+0 = a[0] так как a[0][0] -- нулевой элемент в этом массиве и его адрес это адрес
начала всего массива.