4 разных способа использования двумерных массивов с указателями, что правильно? Объяснение очень помогло бы

Четыре приведенные ниже программы вводят двумерный массив, а затем распечатывают его.

  • Первый выводит мусорные значения, а также выдает несколько предупреждений (чего я не понял).
  • Второй работает правильно, и это имеет смысл, поскольку я считал, что 2D-массив хранится в памяти линейно.
  • Третий работает правильно, но я понятия не имею, почему он работает.
  • 4-й тоже работает.

  • Так что было бы очень полезно, если бы кто-нибудь мог объяснить, как работает каждый из методов, ура

Боюсь, мое понимание того, как работают указатели, не так хорошо, как я думал.

int main(){
        int n; 
        int a [3][4];
        int i=0,j=0,count=0;
        for(i=0;i<3;i++){
            for(j=0;j<4;j++){
                scanf("%d",(a+4*i+j)); // Output-514623632  514623648   514623664   514623680   514623696   514623712   514623728   514623744   514623760   514623776   514623792   514623808   
            }
        }
        for(i=0;i<3;i++){
            for(j=0;j<4;j++){
                printf("%d\t",*(a+4*i+j));
            }
        }


        return 0;
    }

Warnings -solution.c:15:21: warning: format ‘%d’ expects argument of type ‘int *’, but argument 2 has type ‘int (*)[4]’ [-Wformat=]
         scanf("%d",(a+4*i+j));
solution.c:20:22: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘int *’ [-Wformat=]
         printf("%d\t",*(a+4*i+j));
                 ~^     ~~~~~~~~~
                 %ls 

int main(){
    int n; 
    int a [3][4];
    int i=0,j=0,count=0;
    for(i=0;i<3;i++){
        for(j=0;j<4;j++){
            scanf("%d",*(a+4*i+j));
        }
    }
    for(i=0;i<3;i++){
        for(j=0;j<4;j++){
            printf("%d\t",**(a+4*i+j)); // Output -1 2 3 4 5 6 7 8 9 10 11 12
        }
    }


    return 0;
}

int main(){
    int n; 
    int a [3][4];
    int i=0,j=0,count=0;
    for(i=0;i<3;i++){
        for(j=0;j<4;j++){
            scanf("%d",(*(a+i)+j));
        }
    }
    for(i=0;i<3;i++){
        for(j=0;j<4;j++){
            printf("%d\t",*(*(a+i)+j));  // Output- 1 2 3 4 5 6 7 8 9 10 11 12
        }
    }


    return 0;
}

int main(){
    int n; 
    int * a=(int*)malloc(12*sizeof(int)) ;
    int i=0,j=0,count=0;
    for(i=0;i<3;i++){
        for(j=0;j<4;j++){
            *(a+4*i+j)=++count;
        }
    }
    for(i=0;i<3;i++){
        for(j=0;j<4;j++){
            printf("%d\n",*(a+4*i+j));   // Output- 1 2 3 4 5 6 7 8 9 10 11 12
        }
    }


    return 0;
}

person Deepak D    schedule 29.09.2017    source источник
comment
Второй и третий также не являются допустимыми ссылками на измерения (хотя они работают из-за размеров распределения по умолчанию), а четвертый является динамическим. Предложите использовать «int a[3*4]».   -  person dwn    schedule 29.09.2017
comment
как-то связано   -  person    schedule 29.09.2017
comment
@dwn, второе и третье, являются абсолютно допустимыми размерами. Они распределены статически.   -  person Aashish    schedule 29.09.2017
comment
@Deepak D третий имеет наиболее распространенный способ доступа к массиву с помощью указателей.   -  person Aashish    schedule 29.09.2017
comment
пройдите через это: четыре разных способа   -  person Aashish    schedule 29.09.2017
comment
@Aashish, второе недействительно, но третье...   -  person    schedule 29.09.2017
comment
Правильный способ — не использовать указатели вообще, а использовать обычный оператор нижнего индекса []. Я понимаю, что хочу сделать это в качестве учебного упражнения, но на практике вашему коду будет намного легче следовать (и у вас будет меньше шансов совершить ошибку), если вы просто напишете a[i][j].   -  person John Bode    schedule 29.09.2017
comment
Хорошо, да, третий - быстро сканировал   -  person dwn    schedule 30.09.2017


Ответы (2)


Кажется, это сводится к распространенному заблуждению о том, что «массив — это указатель» — это неправда.

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

Теперь взгляните на свой массив:

int a[3][4];

Здесь a — это массив из 3 элементов, а тип элемента — int ()[4]. Это напрямую объясняет, почему ваш первый пример не работает должным образом. В вашем выражении a оценивается как указатель на первый элемент, поэтому тип равен int (*)[4], а а не просто int *, как вы ожидаете. Таким образом, a+1 добавит к значению указателя в 4 раза больше, чем int. С этим объяснением вы также должны понимать предупреждения, которые вы получаете.

Также обратите внимание, что по той же причине второй пример неверен. Добавленный оператор разыменования делает используемые типы правильными, но вы по-прежнему обращаетесь к недопустимым местоположениям, поскольку арифметика ваших указателей по-прежнему работает с int (*)[4]. Конечно, нет никакой гарантии, что "провалится", это просто неопределенное поведение.

Единственная правильная версия с использованием 2d-массива — это третий пример:

(a+i) является указателем на i-й элемент a, разыменование которого дает массив типа int ()[4], и это снова оценивается как указатель на его первый элемент, поэтому добавление к нему j дает вам местоположение желаемого значения.

Четвертый пример — это не двумерный массив, это динамическое распределение, используемое как плоский массив, поэтому здесь это не так интересно. Если вы хотите динамически выделить 2d-массив, вам нужно будет написать, например:

int (*a)[4] = malloc(3 * sizeof *a);

Теперь вернемся к моему первому абзацу: с int a[3][4] у вас есть (void *)a == (void *)*a, потому что первый элемент a начинается в том же месте, что и первый элемент a[0], различаются только типы a и *a. Это было бы невозможно, если бы массивы и указатели были одним и тем же.

person Community    schedule 29.09.2017

(a+4*i+j): компилятор жалуется на тип, потому что a не относится к типу int *. Вы объявили его как int a[3][4], который обычно читается как двумерный массив целых чисел размером 3x4. На самом деле a представляет собой массив из 3 массивов по 4 целых числа. Тогда a — это адрес первого массива из 4 целых чисел. Помните, что определение type v[N] определяет v как адрес первого элемента массива, и что a может распадаться на указатель на тип элемента. Тогда в вашем случае a распадается на указатель на массив из 4 целых чисел. Вот почему компилятор жалуется, что %d нужен соответствующий аргумент типа int *.

Даже если бы адрес мог быть правильным, арифметика на нем не то, что вы думаете. a+3*i+j означает адрес 3*i+j-го массива из 4 целых чисел, начиная с адреса a.

Вы можете заставить распадаться на int *:

int *ta = (int *)a; // ta contains the same address but of different type...
int i=0,j=0,count=0;
for(i=0;i<3;i++){
    for(j=0;j<4;j++){
        scanf("%d",(ta+4*i+j)); // arithmetic is the right one (ints by ints)
    }
}
for(i=0;i<3;i++){
    for(j=0;j<4;j++){
        printf("%d\t",*(ta+4*i+j));
    }
}

То же самое и для второго примера, арифметика неверна.

Третий и четвертый правильные. Третий, потому что вы переходите (с i) к правильному массиву, а затем переходите в этом к правильному int.

Четвертый почти такой же, как код, который я дал.

---РЕДАКТИРОВАТЬ---

Как сказал Феликс в комментариях, будьте осторожны, даже если это работает во многих распространенных архитектурах/ОС/компиляторах, существует строгое правило псевдонимов, которое не позволяет вам это делать. Прочтите ссылку, которую он предоставил, чтобы узнать больше.

person Jean-Baptiste Yunès    schedule 29.09.2017
comment
Хотя на практике это, скорее всего, сработает, есть подводные камни, поскольку это нарушает строгое сглаживание... см. этот вопрос. - person ; 29.09.2017
comment
Да, я знаю, но псевдонимы пока не его проблема. - person Jean-Baptiste Yunès; 29.09.2017
comment
Ну да, я только думаю, что не совсем соответствующий код в ответе требует отказа от ответственности, так что спасибо за редактирование! - person ; 29.09.2017
comment
Хорошо, я заметил и в следующий раз буду гораздо внимательнее. - person Jean-Baptiste Yunès; 29.09.2017