Страницы

Поиск по вопросам

четверг, 5 марта 2020 г.

Странности при работе с двумерным массивом в си

#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] -- нулевой элемент в этом массиве и его адрес это адрес начала всего массива.

Комментариев нет:

Отправить комментарий