Разница между ** переменной и переменной [] []? - PullRequest
2 голосов
/ 12 февраля 2012

Я не понимаю, почему я должен получать содержимое двумерного массива в b[][3], а не в **b?Также, как мы можем сделать вызов по значению для двумерных массивов?Также адрес двумерного массива arr равен содержанию arr, равен *arr, равен &arr[0][0];все адреса одинаковы.Я не могу ясно представить это;Может кто-нибудь объяснить мне, как на самом деле хранится многомерный массив.«Полезные графические ссылки приветствуются».

#include "hfile.h" // contains all needed H files

void caller(int b[][3])  // why can't we write **b?
{
    int k=100;
    printf("\n****Caller:****\n");

    for(int i=0;i<3;i++)
    {
        for(int j=0;j<3;j++)
        {
            b[i][j]=k++;
            printf("\t %d",b[i][j]);
        }
        printf("\n");
    }
}

int main()
{
    int arr[3][3]={1,2,3,4,5,6,7,8,9}; // original containts

    caller(arr);              // Called caller function passing containts of "arr"

    printf("\n****Orignal****\n");
    for(int i=0;i<3;i++)
    {
        for(int j=0;j<3;j++)
            printf("\t %d",arr[i][j]);           

        printf("\n");
    }
    return 0;
}

Ответы [ 3 ]

5 голосов
/ 12 февраля 2012

Если вы объявляете многомерный массив:

int b[M][N];

хранилище смежно. Так, когда вы получаете доступ к элементу, например, (x = b[i][j];), компилятор создает код, эквивалентный этому:

int *c = (int *)b;  // Treat as a 1D array
int  k = (i*N + j); // Offset into 1D array
x = c[k];

Когда вы обращаетесь к элементу через указатель-указатель, компилятор не знает об измерениях и выдает код, подобный следующему:

int *t = b[i];  // Follow first pointer (produces another pointer)
x = t[j];       // Follow second pointer

т.е.. он просто следует указателям.

Они полностью несовместимы, поэтому компилятор не позволяет передавать истинный 2D-массив в функцию, принимающую указатель на указатель.

1 голос
/ 12 февраля 2012

ASCII Art Rules!

Давайте наглядно рассмотрим 2D-массив.Давайте предположим, что массив состоит из 2-байтовых short целых чисел, и что адреса также удобно являются 2-байтовыми.Это может быть чип Zilog Z80, если хотите, но только для удобства сохранения небольших чисел.

short A[3][3];

+---------+---------+---------+
| A[0][0] | A[0][1] | A[0][2] |
+---------+---------+---------+
| A[1][0] | A[1][1] | A[1][2] |
+---------+---------+---------+
| A[2][0] | A[2][1] | A[2][2] |
+---------+---------+---------+

Давайте предположим адрес: A = 0x4000.Тогда адреса short * элементов массива:

&A[0][0] = 0x4000;
&A[0][1] = 0x4002;
&A[0][2] = 0x4004;
&A[1][0] = 0x4006;
&A[1][1] = 0x4008;
&A[1][2] = 0x400A;
&A[2][0] = 0x400C;
&A[2][1] = 0x400E;
&A[2][2] = 0x4010;

Теперь также следует заметить, что вы можете написать:

&A[0]    = 0x4000;
&A[1]    = 0x4006;
&A[2]    = 0x400C;

Типы этихуказатели - это 'указатель на массив [3] из short', или short (*A)[3].

Вы также можете написать:

&A       = 0x4000;

Тип этого указателя: 'указатель на массив [3] [3] из short 'или short (*A)[3][3].

Одно из ключевых отличий заключается в размерах объекта, поскольку этот код демонстрирует:

#include <stdio.h>
#include <inttypes.h>

static void print_address(const char *tag, uintptr_t address, size_t size);

int main(void)
{
    char  buffer[32];
    short A[3][3] = { { 0, 1, 2 }, { 3, 4, 5 }, { 6, 7, 8 } };
    int i, j;

    print_address("A",  (uintptr_t)A,  sizeof(A));
    print_address("&A", (uintptr_t)&A, sizeof(*(&A)));

    for (i = 0; i < 3; i++)
    {
        for (j = 0; j < 3; j++)
        {
            sprintf(buffer, "&A[%d][%d]", i, j);
            print_address(buffer, (uintptr_t)&A[i][j], sizeof(*(&A[i][j])));
        }
    }

    for (i = 0; i < 3; i++)
    {
        sprintf(buffer, "&A[%d]", i);
        print_address(buffer, (uintptr_t)&A[i], sizeof(*(&A[i])));
    }

    putchar('\n');
    for (i = 0; i < 3; i++)
    {
        for (j = 0; j < 3; j++)
        {
            printf("  A[%d][%d] = %d", i, j, A[i][j]);
        }
        putchar('\n');
    }

    return 0;
}

static void print_address(const char *tag, uintptr_t address, size_t size)
{
    printf("%-8s  = 0x%.4" PRIXPTR " (size %zu)\n", tag, address & 0xFFFF, size);
}

ЭтоПрограмма подделывает 16-битные адреса с помощью операции маскирования в функции print_address().

Вывод при компиляции в 64-битном режиме на MacOS X 10.7.2 (GCC 'i686-apple-darwin11-llvm-gcc-4.2 (GCC) 4.2.1 (на основе Apple Inc., сборка 5658) (LLVM, сборка 2335.15.00) '), было:

A         = 0xD5C0 (size 18)
&A        = 0xD5C0 (size 18)
&A[0][0]  = 0xD5C0 (size 2)
&A[0][1]  = 0xD5C2 (size 2)
&A[0][2]  = 0xD5C4 (size 2)
&A[1][0]  = 0xD5C6 (size 2)
&A[1][1]  = 0xD5C8 (size 2)
&A[1][2]  = 0xD5CA (size 2)
&A[2][0]  = 0xD5CC (size 2)
&A[2][1]  = 0xD5CE (size 2)
&A[2][2]  = 0xD5D0 (size 2)
&A[0]     = 0xD5C0 (size 6)
&A[1]     = 0xD5C6 (size 6)
&A[2]     = 0xD5CC (size 6)

  A[0][0] = 0  A[0][1] = 1  A[0][2] = 2
  A[1][0] = 3  A[1][1] = 4  A[1][2] = 5
  A[2][0] = 6  A[2][1] = 7  A[2][2] = 8

Я скомпилировал вариант без операции маскирования в 32-разрядной версиирежим и получил вывод:

A         = 0xC00E06D0 (size 18)
&A        = 0xC00E06D0 (size 18)
&A[0][0]  = 0xC00E06D0 (size 2)
&A[0][1]  = 0xC00E06D2 (size 2)
&A[0][2]  = 0xC00E06D4 (size 2)
&A[1][0]  = 0xC00E06D6 (size 2)
&A[1][1]  = 0xC00E06D8 (size 2)
&A[1][2]  = 0xC00E06DA (size 2)
&A[2][0]  = 0xC00E06DC (size 2)
&A[2][1]  = 0xC00E06DE (size 2)
&A[2][2]  = 0xC00E06E0 (size 2)
&A[0]     = 0xC00E06D0 (size 6)
&A[1]     = 0xC00E06D6 (size 6)
&A[2]     = 0xC00E06DC (size 6)

  A[0][0] = 0  A[0][1] = 1  A[0][2] = 2
  A[1][0] = 3  A[1][1] = 4  A[1][2] = 5
  A[2][0] = 6  A[2][1] = 7  A[2][2] = 8

А в 64-битном режиме выход из варианта был:

A         = 0x7FFF65BB15C0 (size 18)
&A        = 0x7FFF65BB15C0 (size 18)
&A[0][0]  = 0x7FFF65BB15C0 (size 2)
&A[0][1]  = 0x7FFF65BB15C2 (size 2)
&A[0][2]  = 0x7FFF65BB15C4 (size 2)
&A[1][0]  = 0x7FFF65BB15C6 (size 2)
&A[1][1]  = 0x7FFF65BB15C8 (size 2)
&A[1][2]  = 0x7FFF65BB15CA (size 2)
&A[2][0]  = 0x7FFF65BB15CC (size 2)
&A[2][1]  = 0x7FFF65BB15CE (size 2)
&A[2][2]  = 0x7FFF65BB15D0 (size 2)
&A[0]     = 0x7FFF65BB15C0 (size 6)
&A[1]     = 0x7FFF65BB15C6 (size 6)
&A[2]     = 0x7FFF65BB15CC (size 6)

  A[0][0] = 0  A[0][1] = 1  A[0][2] = 2
  A[1][0] = 3  A[1][1] = 4  A[1][2] = 5
  A[2][0] = 6  A[2][1] = 7  A[2][2] = 8

В 32-битном и большом количестве шума64-битные версии адресов, такмы можем использовать псевдо 16-битную версию адреса.

Обратите внимание, что адрес A[0][0] совпадает с адресом A[0] и A, но с размерами объектауказал на другое.&A[0][0] указывает на одно (короткое) целое число;&A[0] указывает на массив из 3 (коротких) целых чисел;&A указывает на массив из 3x3 (коротких) целых чисел.

Теперь нам нужно посмотреть, как работает short **;это работает совсем по-другому.Вот некоторый тестовый код, связанный с предыдущим примером, но отличающийся от него.

#include <stdio.h>
#include <inttypes.h>

static void print_address(const char *tag, uintptr_t address, size_t size);

int main(void)
{
    char  buffer[32];
    short t[3] = { 99, 98, 97 };
    short u[3] = { 88, 87, 86 };
    short v[3] = { 77, 76, 75 };
    short w[3] = { 66, 65, 64 };
    short x[3] = { 55, 54, 53 };
    short y[3] = { 44, 43, 42 };
    short z[3] = { 33, 32, 31 };
    short *a[3] = { t, v, y };
    short **p = a;
    int i, j;

    print_address("t",  (uintptr_t)t,  sizeof(t));
    print_address("u",  (uintptr_t)u,  sizeof(u));
    print_address("v",  (uintptr_t)v,  sizeof(v));
    print_address("w",  (uintptr_t)w,  sizeof(w));
    print_address("x",  (uintptr_t)x,  sizeof(x));
    print_address("y",  (uintptr_t)y,  sizeof(y));
    print_address("z",  (uintptr_t)z,  sizeof(z));

    print_address("a",  (uintptr_t)a,  sizeof(a));
    print_address("&a", (uintptr_t)&a, sizeof(*(&a)));

    for (i = 0; i < 3; i++)
    {
        for (j = 0; j < 3; j++)
        {
            sprintf(buffer, "&a[%d][%d]", i, j);
            print_address(buffer, (uintptr_t)&a[i][j], sizeof(*(&a[i][j])));
        }
    }

    for (i = 0; i < 3; i++)
    {
        sprintf(buffer, "&a[%d]", i);
        print_address(buffer, (uintptr_t)&a[i], sizeof(*(&a[i])));
    }

    putchar('\n');
    for (i = 0; i < 3; i++)
    {
        for (j = 0; j < 3; j++)
        {
            printf("  a[%d][%d] = %d", i, j, a[i][j]);
        }
        putchar('\n');
    }

    putchar('\n');
    print_address("p",  (uintptr_t)p,  sizeof(*(p)));
    print_address("&p", (uintptr_t)&p, sizeof(*(&p)));

    for (i = 0; i < 3; i++)
    {
        for (j = 0; j < 3; j++)
        {
            sprintf(buffer, "&p[%d][%d]", i, j);
            print_address(buffer, (uintptr_t)&p[i][j], sizeof(*(&p[i][j])));
        }
    }

    for (i = 0; i < 3; i++)
    {
        sprintf(buffer, "&p[%d]", i);
        print_address(buffer, (uintptr_t)&p[i], sizeof(*(&p[i])));
    }

    putchar('\n');
    for (i = 0; i < 3; i++)
    {
        for (j = 0; j < 3; j++)
        {
            printf("  p[%d][%d] = %d", i, j, p[i][j]);
        }
        putchar('\n');
    }

    return 0;
}

static void print_address(const char *tag, uintptr_t address, size_t size)
{
    printf("%-8s  = 0x%.4" PRIXPTR " (size %zu)\n", tag, address & 0xFFFF, size);
}

Эта программа состоит из двух частей.Одна половина рассекает массив a;другой рассекает двойной указатель p.Вот некоторые примеры ASCII, которые помогут понять это:

+------+------+------+                      +------+------+------+
|  99  |  98  |  97  |    t = 0x1000        |  88  |  87  |  86  |    u = 0x1100
+------+------+------+                      +------+------+------+

+------+------+------+                      +------+------+------+
|  77  |  76  |  75  |    v = 0x1200        |  66  |  65  |  64  |    w = 0x1300
+------+------+------+                      +------+------+------+

+------+------+------+                      +------+------+------+
|  55  |  54  |  53  |    x = 0x1400        |  44  |  43  |  42  |    y = 0x1500
+------+------+------+                      +------+------+------+

+------+------+------+
|  33  |  32  |  31  |    z = 0x1600
+------+------+------+

+--------+--------+--------+
| 0x1000 | 0x1200 | 0x1500 |    a = 0x2000
+--------+--------+--------+

+--------+
| 0x2000 |                      p = 0x3000
+--------+

Обратите внимание, что массивы t .. z расположены в «произвольных» местах - не смежно на диаграмме.Возможно, что некоторые массивы будут глобальными переменными, например, из другого файла, а другие будут статическими переменными в том же файле, но вне функции, а другие будут статическими, но локальными для функции, а такжеэти локальные автоматические переменные.Вы можете видеть, как p является переменной, содержащей адрес;адрес - это адрес массива a.В свою очередь, массив a содержит 3 адреса, адреса 3 других массивов.

Это выходные данные 64-битной компиляции программы, искусственно разделенные.Он имитирует 16-разрядные адреса, маскируя все, кроме последних 4 цифр шестнадцатеричного адреса.

t         = 0x75DA (size 6)
u         = 0x75D4 (size 6)
v         = 0x75CE (size 6)
w         = 0x75C8 (size 6)
x         = 0x75C2 (size 6)
y         = 0x75BC (size 6)
z         = 0x75B6 (size 6)

Это предотвращает предупреждения о неиспользуемых переменных, а также идентифицирует адреса 7 массивов из 3 целых чисел.

a         = 0x7598 (size 24)
&a        = 0x7598 (size 24)
&a[0][0]  = 0x75DA (size 2)
&a[0][1]  = 0x75DC (size 2)
&a[0][2]  = 0x75DE (size 2)
&a[1][0]  = 0x75CE (size 2)
&a[1][1]  = 0x75D0 (size 2)
&a[1][2]  = 0x75D2 (size 2)
&a[2][0]  = 0x75BC (size 2)
&a[2][1]  = 0x75BE (size 2)
&a[2][2]  = 0x75C0 (size 2)
&a[0]     = 0x7598 (size 8)
&a[1]     = 0x75A0 (size 8)
&a[2]     = 0x75A8 (size 8)

  a[0][0] = 99  a[0][1] = 98  a[0][2] = 97
  a[1][0] = 77  a[1][1] = 76  a[1][2] = 75
  a[2][0] = 44  a[2][1] = 43  a[2][2] = 42

Обратите внимание на важные отличия.Размер a теперь составляет 24 байта, а не 18, потому что это массив из 3 (64-битных) указателей.Размер &a[n] составляет 8 байтов, потому что каждый является указателем.Способ загрузки данных в расположение массива также совершенно другой - вам придется взглянуть на ассемблер, чтобы увидеть это, поскольку исходный код на Си выглядит одинаково.

В коде двумерного массиваоперация загрузки для A[i][j] вычисляет:

  • байтовый адрес A
  • добавляет (3 * i + j) * sizeof(short) к этому байтовому адресу
  • извлекает 2-байтовое целое число из этогоадрес.

В массиве кода указателя операция загрузки для A[i][j] вычисляет:

  • байтовый адрес a
  • добавляет i * sizeof(short *) по этому байтовому адресу
  • выбирает байтовый адрес из этого вычисленного значения, вызывает его b
  • добавляет j * sizeof(short) к b
  • выбирает 2-байтовое целоес адреса b

Вывод для p несколько отличается.Обратите внимание, в частности, адрес в p отличается от адреса из p.Тем не менее, после того, как вы это пройдете, поведение в основном будет таким же.

p         = 0x7598 (size 8)
&p        = 0x7590 (size 8)
&p[0][0]  = 0x75DA (size 2)
&p[0][1]  = 0x75DC (size 2)
&p[0][2]  = 0x75DE (size 2)
&p[1][0]  = 0x75CE (size 2)
&p[1][1]  = 0x75D0 (size 2)
&p[1][2]  = 0x75D2 (size 2)
&p[2][0]  = 0x75BC (size 2)
&p[2][1]  = 0x75BE (size 2)
&p[2][2]  = 0x75C0 (size 2)
&p[0]     = 0x7598 (size 8)
&p[1]     = 0x75A0 (size 8)
&p[2]     = 0x75A8 (size 8)

  p[0][0] = 99  p[0][1] = 98  p[0][2] = 97
  p[1][0] = 77  p[1][1] = 76  p[1][2] = 75
  p[2][0] = 44  p[2][1] = 43  p[2][2] = 42

Все это было в одной (основной) функции.Вам нужно будет проводить свои собственные параллельные эксперименты, передавая различные указатели на функции и получая доступ к массивам за этими указателями.

0 голосов
/ 12 февраля 2012
void caller(int b[][3])  // why can't we write **b ?

Вы можете написать int **b, но затем нельзя передать arr этой функции, поскольку arr определяется как int arr[3][3], что несовместимо с типом int **.

arr может конвертироваться в int (*)[3], но не в int **. Таким образом, вы можете написать это:

void caller(int (*b)[3])  //ok

На самом деле int[3][3] определяет массив массива 1 , тогда как int** определяет указатель на указатель. int[3][3] может преобразовать в указатель на массив 3 int (что составляет int (*)[3]), точно так же, как int[3] может преобразовать в указатель на int (что является int*).

1. Точнее, он определяет массив из 3 array-of-3-int.

...