C Урок 9. Массивы. Часть 2



В предыдущей части нашего занятия мы провели краткое знакомство с массивами, изучили одномерные массивы и испытали их работу на практике.

А теперь давайте познакомимся с таким понятием, как многомерный массив.

Многомерный массив — это массив, элементы которого упорядочены в нескольких измерениях. Многомерные массивы могут быть двумерными, трёхмерными и так далее.

Двумерный массив — это массив из нескольких одномерных массивов.

Далее мы можем считать, что трёхмерный массив — это массив из нескольких двумерных массивов и т.д., то есть массив у которого будет три измерения.

Как же нам понять, что это за измерения такие у массивов.

Можно в качестве ассоциации взять отрезок, у которого существует только длина, и взять прямоугольник, у которого есть ещё и ширина, а вот у прямоугольного параллелепипеда есть также ещё и высота.

Так и у наших многомерных массивов существует несколько измерений, это своего рода матрицы.

Вот так вот выглядит объявление целочисленного двумерного массива

 

int a[4][3];

 

Мы объявили массив из 4 строк и 3 столбцов.

По сути мы получили 4 массива по 3 элемента.

Двумерный массив мы можем представить как матрицу из нескольких строк и нескольких столбцов

 

 

Вот таким образом мы можем в коде заполнить двумерный массив значениями

 

a[0][0] = 15; a[0][1] = 24; a[0][2] = -55;

a[1][0] = 33; a[1][1] = -4; a[1][2] = -5;

a[2][0] = 45; a[2][1] = -31; a[2][2] = 5;

a[3][0] = 81; a[3][1] = 46; a[3][2] = 0;

 

Получится вот так

 

 

На этапе объявления массива мы можем произвести инициализацию вот таким образом

 

int a[4][3] = {15, 24,-55, 33, -4, -5, 45, -31, 5, 81, 46, 0};

 

Скорее всё пройдёт с тем же результатом, но любой уважающий себя компилятор как минимум выдаст нам предупреждение о пропущенных скобках.

Потому что правильная инициализация многомерного массива на этапе инициализации должна быть вот такой

 

int a[4][3] = {{15, 24,-55}, {33, -4, -5}, {45, -31, 5}, { 81, 46, 0}};

 

Здесь даже лучше виден смысл многомерного массива, здесь очевидно, что это массив, состоящий из четырёх трёхэлементных массивов.

Давайте поиграем с такими массивами в нашем коде, с массивами из нескольких строк разберёмся несколько позднее.

Перейдём в main() и в самом начале тела данной функции добавим ещё одну переменную

 

unsigned char i, j;

 

 

Закомментируем предыдущий код и добавим вот такой, в котором мы объявим массив размерностью 4 на 3, затем проинициализируем его элементы и выведем их на экран в виде матрицы

 

 

Посмотрим, что у нас получилось

 

 

А получилось всё очень даже красиво и правильно.

Теперь попробуем инициализацию на этапе объявления.

Также закомментируем старый код и добавим новый

 

 

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

Также интересна работа с многомерными символьными массивами, которая имеет ещё некоторые особенности.

Двумерный символьный массив по сути представляет собой массив из нескольких строк.

Обычные посимвольные способы инициализации мы рассматривать здесь не будем, они не так интересны и редко используются.

Работать будем сразу с нашим кодом, он будет нам служить сразу и теоретической частью.

Закомментируем также предыдущий код.

Мы можем также сначала объявить затем проинициализировать массив из нескольких строк. Для этого мы воспользуемся библиотечной функцией копирования строк, для чего сначала подключим данную библиотеку

 

 

Закомментируем наши счётчики, они нам больше пока не понадобятся

// unsigned char i, j;

Затем добавим следующий код

 

 

В данном коде мы объявили двумерный массив размерностью 3 на 30, состоящий из 3 символьных массивов по 30 элементов. Затем мы воспользовались функцией копирования строк strcpy, которая в копирует сроку, оканчивающуюся нулём, указатель на которую находится во втором параметре в определённое место памяти, на которое указывает первый параметр.

А первый параметр у нас указывает на элемент двумерного массива, поэтому здесь мы показываем только один индекс — индекс верхнего уровня. Второй параметр указывает также на место в памяти, где находится строка, которую мы написали в кавычках.

Соберём наш код и посмотрим, что у нас получилось

 

 

Все наши строки благополучно отобразились в консоли.

Теперь попробуем инициализацию наших строк на этапе объявления массива

 

 

Мы объявили массив размером 3 на 32, так будет удобнее для просмотра памяти.

Если мы посмотрим работу нашего кода, то мы получим тот же результат.

 

 

Давайте попробуем посмотреть, как разместились символы наших строк в памяти при работе программы, для этого зайдём в отладку с помощью следующей команды

gdb myprog09

Далее, попав в отладчик установим точку останова на функцию main

 

 

Установим дизассемблирование в формат Intel

 

 

Запустим программу на выполнение, программа остановится на входе в main

 

 

Запустим дизассемблирование

 

 

Вам не обязательно заходить в отладку, можете просто посмотреть и мы обязательно сейчас найдём наш массив строк. Пока мы находимся в такой точке, когда мы ещё наш массив не заполняли, но если посмотреть внимательно на код, то мы можем увидеть откуда именно массив будет заполняться, то есть увидеть источник хранения наших строк до их внесения в массив. А находится он по адресу с шестнадцатеричным значением 0x404020. Посмотрим участок памяти с данного адреса с при помощи вот такой команды, с помощью которой мы увидим в символьном выражении, а также в кодовом, 100 байт памяти

 

 

Мы видим здесь дамп байтов по 8 байтов в каждой строке и видим, что наши строки расположились по адресам 0x404020, 0x404040 и 0x404060. То есть мы заняли блок памяти размером в 96 байт под наши три небольших строки. Это, конечно, очень расточительно, если мы не планируем писать в наши элементы массивов впоследствии в коде более длинные строки. Давайте теперь шагнём по программе с помощью вот такой вот простенькой команды

 

 

Тем самым мы выполнили команду объявления и инициализации массива. Жаль, что мы не можем шагать по каждой ассемблерной команде, для этого надо как минимум запустить OllyDebuger или Eclipse, но мы пока работаем не в среде и первый для нас тоже будет тяжеловат. Но ничего страшного.

Перейдём ещё раз в окно дизассемблирования с помощью команды disas и посмотрим вот на этот код

 

 

Для тех, кто не знает, или не совсем хорошо знает ассемблер (вообще-то переходить к высокоуровневым языкам типа C нужно уже зная ассемблер), я постараюсь вкратце по-простому объяснить, что здесь происходит. Копируем мы участок памяти при помощи команды movs с префиксом rep, который заставляет повторять процесс копирования столько раз, сколько мы задали, занеся определённое число в регистр ecx. При каждом повторении копирования значение в этом регистре будет убавляться на 1, то есть декрементироваться. А в регистре у нас находится число 0x18. «А у нас ведь больше байтов!», — скажет кто-то. А дело в том, что нас ведь стоит DWORD PTR, которое говорит о том, что мы оперируем 4-байтными величинами, поэтому получится 0x18x4 = 0x60, что и составляет в десятичном выражении 96. Копирование происходит из участка памяти, адрес которого находится в регистре esi, то есть это и есть наш 0x404020, в участок памяти, на который показывает регистр edi, а это esp + 0x10. esp — это регистр, в котором находится адрес стека.

Посмотрим этот адрес, заодно посмотрим содержание всех регистров

 

 

Поэтому строки наши должны будут скопироваться в участок памяти по адресу 0x61feb0+0x10=0x61fec0.

Вот давайте их там и поищем

 

 

Там они и находятся.

Таким образом, мы видим, что мы уже заняли память вдвое больше.

Продолжим выполнение нашей программы, чтобы она корректно завершилась

 

 

Теперь выйдем из отладки

 

 

Вернёмся в наш исходный код, закомментируем предыдущий код и добавим подобную инициализацию, только использовать мы будем для наших строк безразмерный массив

 

 

Результат выполнения данного кода визуально будет таким же

 

 

Но давайте и теперь зайдём в отладку, а затем в дизассемблер, установив также точку останова на main

 

 

В данном случае до объявления массива, как мы видим из кода, наши строки находятся по адресам 0x405044, 0x405053 и 0x405059. Чтобы их увидеть, достаточно посмотреть дамп с первого адреса, причём много байт нам теперь смотреть ни к чему

 

 

Мы видим, что строки наши теперь расположились вполне плотненько, между ними только завершающие нули.

Сделаем один шаг по программе, чтобы наш массив проинициализировался

 

 

Перейдём снова в дизассемблирование

 

 

Из кода мы видим, что теперь наши строки находятся по адресам, находящимся esp+0x14, esp+0x18 и esp+0x1с.

Давайте найдём эти адреса, для чего сначала узнаем адрес вершины стека

 

 

Теперь, произведя нехитрые вычисления, мы знаем, что адреса наши находятся в ячейках 0x61ff24, 0x61ff28 и 0x61ff2с.

Только помним, что там не сами строки, а только адреса тех участков памяти, где эти строки находятся.

Узнаем теперь эти адреса с помощью вот такой команды

 

 

В принципе мы просто проверили работу кода. На самом деле мы эти адреса уже видели, то есть у нас строки остались в том месте, где и были, в стеке лежат просто их указатели, для функции printf библиотеки msvcrt.dll этого достаточно. Вот такая вот экономия памяти получилась. Правда потом из стека эти адреса копируются в регистры, потом опять в стек в обратном порядке. Если бы мы писали программу на ассемблере, мы бы сразу разместили наши строки туда, куда надо. Но таковы особенности языков, там много чего лишнего происходит, но этим мы платим за удобство программирования и это не такая уж большая цена при наших вычислительных возможностях на данный момент.

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

Это вовсе не значит, что мы узнали о массивах всё, мы постоянно будем с ними работать и знания наши будут постепенно совершенствоваться, так как предела этому, как известно не существует.

Всем спасибо за внимание, надеюсь урок был для вас полезен.

 

 

Предыдущая часть Программирование на C Следующий урок

 

Исходный код

 

 

Смотреть ВИДЕОУРОК (нажмите на картинку)

 

C Массивы

2 комментария на “C Урок 9. Массивы. Часть 2
  1. SergV:

    Большое спасибо! очень нравится когда показываете отладку!

  2. Пытался инициализировать трехмерный массив:
    unsigned char i, j, k;
    int n[3][4][3] = {10, 25, 33, 71,
    45, 77, 44, 18,
    38, 84, 21, 99, 10, 25, 33, 71,
    45, 77, 44, 18,
    38, 84, 21, 99, 10, 25, 33, 71,
    45, 77, 44, 18,
    38, 84, 21, 99};

    for (i=0;i<3;i++)
    {
    for (j=0;j<4;j++)
    {
    for (k=0;k<3;j++)
    {
    printf("%d ", n[i][j][k]);
    }
    }
    printf("\r\n");
    }
    Как то не особо получилось.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*