Почему *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
Другие вопросы по тегам