Почему *num не показывает нулевое значение элемента?
В этом коде:
#include<stdio.h>
int main()
{
int num[2] = {20, 30};
printf("%d", num);
printf("%d", &num[0]);
return 0;
}
Насколько я знаю, оба оператора printf будут печатать адрес первого элемента в num
потому что в первом утверждении num
это указатель на int.
Но если num
является указателем, то он также должен иметь любой адрес, кроме печати его адреса (с printf("%d", &num)
), он показывает адрес первого элемента.
В двумерном массиве все это тоже сбивает с толку:
#include<stdio.h>
int main(void)
{
int num[ ] [2]={20,30,40,50};
printf("%d",*num);
return 0;
}
Эта программа печатает адрес нулевого элемента, который является адресом num[0][0]
, Но почему он это делает? Почему не печатается сохраненное в нем значение, так как все они имеют одинаковый адрес (num,num[0]
а также num[0][0]
)?
5 ответов
Обо всем по порядку; переменные массива не являются указателями; они не хранят адрес ни к чему.
Для объявления, такого как
T a[N];
память будет выложена как
+---+
a[0]: | |
+---+
a[1]: | |
+---+
...
+---+
a[N-1]: | |
+---+
Для двумерного массива MxN это будет выглядеть
+---+
a[0][0]: | |
+---+
a[0][1]: | |
+---+
...
+---+
a[0][N-1]: | |
+---+
a[1][0]: | |
+---+
a[1][1]: | |
+---+
...
+---+
a[M-1][N-1]: | |
+---+
Шаблон должен быть очевиден для массивов 3D и выше.
Как видите, для отдельной переменной ни одно хранилище не выделено a
который содержит адрес первого элемента; вместо этого в языке C существует правило, согласно которому выражение типа "массив N-элементов T
"будет преобразован (" распад ") в выражение типа" указатель на T
"и значением выражения будет адрес первого элемента массива, кроме случаев, когда выражение массива является одним из следующих:
- операнд
sizeof
оператор - операнд одинарного
&
оператор - операнд
_Alignof
оператор (C99 и позже) - строковый литерал, используемый для инициализации массива в объявлении
Так с учетом декларации
T a[N];
все следующее верно:
Expression Type Decays to Value
---------- ---- --------- -----
a T [N] T * address of first element, &a[0]
*a T n/a value stored in first element
&a T (*)[N] n/a address of the array, which is
the same as the address of the
first element of the array
a[i] T n/a value stored in the i'th element
&a[i] T * n/a address of the i'th element
sizeof a size_t n/a total number of bytes used by the
array
sizeof *a size_t n/a total number of bytes used by the
first element of the array
sizeof &a size_t n/a total number of bytes used by a
pointer to the array
Выражение a
имеет тип "N-элементный массив T
"; это не операнд одинарного &
или же sizeof
операторы, поэтому он преобразуется в указатель на первый элемент массива, и его значением является адрес этого элемента.
Выражение &a
имеет тип "указатель на массив из N элементов T
"; поскольку a
операнд унарного &
оператор, приведенное выше правило преобразования не применяется (вот почему выражение имеет тип T (*)[N]
вместо T **
). Однако, поскольку адрес массива совпадает с адресом первого элемента массива, он возвращает то же значение, что и выражение a
,
Выражение &a[0]
имеет тип "указатель на T
"и явно указывает на первый элемент массива. Опять же, это значение будет таким же, как и в двух предыдущих выражениях.
Для двумерного массива
T a[M][N];
все следующее верно:
Expression Type Decays to Value
---------- ---- --------- -----
a T [M][N] T (*)[N] address of first subarray, a[0]
*a T [N] T * address pf first subarray, a[0]
&a T (*)[M][N] n/a address of the array, which is
the same as the address of the
first subarray, which is the same
as the address of the first element
of the first subarray.
a[i] T [N] T * address of first element of i'th
subarray
*a[i] T n/a value of first element of i'th subarray
&a[i] T (*)[N] n/a address of the i'th subarray
sizeof a size_t n/a total number of bytes used by the
array
sizeof *a size_t n/a total number of bytes used by the
first subarray
sizeof &a size_t n/a total number of bytes used by a
pointer to the array
Последнее замечание: для распечатки значений указателя используйте %p
спецификатор преобразования и приведение аргумента к (void *)
(это почти единственный раз, когда считается правильным привести указатель на void *
):
printf( " &a yields %p\n", (void *) &a );
printf( " a yields %p\n", (void *) a );
printf( "&a[0] yields %p\n", (void *) &a[0] );
редактировать
Чтобы ответить на вопрос в комментариях:
num, num[] и num[][] - это разные вещи. Существуют разные типы. Здесь num разлагается и становится указателем на указатель, а num[] разлагается и становится указателем на int, а num[][] является целым числом. Правильно?
Не совсем.
Предполагая объявление как
int arr[10][10];
тогда выражение arr
распадется на тип int (*)[10]
(указатель на массив из 10 элементов int
) не int **
; обратитесь к таблице выше снова. В противном случае вы правы; arr[i]
распадется на тип int *
, а также arr[i][j]
будет иметь тип int
,
Выражение типа "N-элементный массив T
указатель "распадается на тип" T
"; если T
это тип массива, то результатом будет "указатель на массив", а не "указатель на указатель".
Посмотрите, как выглядит массив:
int num[ ] [2]={20,30,40,50};
лучше написано как
int num[][2]={{20,30},{40,50}};
Это массив из 2 элементов. Эти 2 элемента, опять же, массивы с 2-мя целыми.
В памяти они выглядят как
20 30 40 50
но разница в том, что num
относится ко всему массиву, num[0]
к первому "частичному массиву" и num[0][0]
к первому элементу первого массива.
У них одинаковый адрес (потому что они начинаются в одном и том же месте), но у них другой тип.
То есть адрес - не единственная важная вещь с указателем, также важен тип.
Во втором примере num
2-мерный массив, или, скажем, массив массивов. Это правда, что *num
это его первый элемент, но этот первый элемент является самим массивом.
Получить num[0][0]
, тебе нужно **num
,
printf("%d\n", **num);
Массивы на самом деле не являются указателями, хотя они, как правило, действуют немного похожим образом, но не всегда.
Скажем, у вас есть этот массив и указатель:
int a[] = {1, 2, 3};
int i = 19;
int *ptr = &i;
Сейчас здесь a is equal to &a
, но то же самое не верно, для указателей (ptr is not equal to &ptr
).
Теперь перейдем к вопросу:
Рассмотрим одномерный массив:
int arr[] = {11, 19, 5, 9};
Здесь элементы этого массива хранятся в смежных местах памяти. Скажем, с начальным адресом 0
:
---------------------
| 11 | 19 | 5 | 9 |
---------------------
0 4 8 12 16
Теперь, когда вы пишете имя массива, arr (для этого примера), вы получите начальный адрес 1-го элемента. Хотя если пишешь &arr
затем вы получите начальный адрес всего блока (включая все элементы массива). Теперь когда пишешь *arr
вы фактически получаете значение внутри 1-го элемента этого массива.
Теперь рассмотрим этот 2-мерный массив arr[][4] = {{11, 19, 5, 9}, {5, 9, 11, 19}}:
0 4 8 12 16 -> These are memory addresses
---------------------
| 11 | 19 | 5 | 9 | ----> These values represent the values inside each index
---------------------
| 5 | 9 | 11 | 19 |
---------------------
16 20 24 28 32
Здесь, когда вы пишете имя массива, как arr
то, что вы получите, это адрес 1-го элемента этого массива, который в этом случае будет адресом этого 0-го индекса:
0 16 32
----------------------------------------------
| 0<sup>th</sup> index | 1<sup>st</sup> index |
----------------------------------------------
Теперь, когда вы делаете &arr
здесь вы получите базовый адрес для всего блока, то есть базовый адрес этого:
0 4 8 12 16
---------------------
| 11 | 19 | 5 | 9 |
---------------------
| 5 | 9 | 11 | 19 |
---------------------
16 20 24 28 32
Теперь, если вы делаете *arr
в 1-мерном массиве это дает вам значение внутри 1-го элемента, хотя в 2-мерном массиве значение в каждом индексе фактически является одним 1-мерным массивом, следовательно, вы получите адрес этого массива:
0 4 8 12 16
---------------------
| 11 | 19 | 5 | 9 |
---------------------
Теперь, если вы делаете **arr
то есть когда вы на самом деле получите значение внутри 1-го элемента, который 11
,
Надеюсь, это прояснит некоторые сомнения:-)
РЕДАКТИРОВАТЬ 1:
Как заметил мой собеседник, кажется, что где-то есть некоторая путаница, хотя я подробно объяснил, что и что означает. Но только для обоснования этого утверждения:
Теперь здесь __a равен &a__, но это не так для указателей (__ptr не равно &ptr__).
Типы обоих a
а также &a
будет отличаться, как уже говорилось, в ответе. Если кто-то выполняет арифметику указателей, он сможет это знать. Попробуйте выполнить a + 1
а также &a + 1
То, как они оба реагируют на арифметику указателей, несомненно, даст хорошую идею.
Учитывая 1-мерный массив:
int arr[] = {11, 19, 5, 9};
---------------------
| 11 | 19 | 5 | 9 |
---------------------
0 4 8 12 16
Мы не можем сделать a++
Правда, для указателя:
int i = 4;
int *ptr = &i;
мы можем выполнить ptr++
, это сделает ptr
указать на следующую ячейку памяти.
Я думаю, что результат означает, что массив на самом деле не указатель, но он конвертируется в указатель в некоторых контекстах, где ожидается указатель, например, передача функции, ожидающей аргумент указателя.
увидеть этот код:
void test(int* num) {
printf("test\n");
printf("%p\n",num);
printf("%p\n",&num);
printf("%p\n",&num[0]);
}
int main(){
int num[2]={20,30};
test(num);
printf("main\n");
printf("%p\n",num);
printf("%p\n",&num);
printf("%p\n",&num[0]);
//other();
return 0;
}
Выход:
test
0x7fff7a422300
0x7fff7a4222e8 //LOOK THIS! Is diferent from main!
0x7fff7a422300
main
0x7fff7a422300
0x7fff7a422300
0x7fff7a422300