Сможете найти глюк ?

Тема в разделе "Флудилка", создана пользователем NR55RU, 27 мар 2014.

  1. NR55RU

    NR55RU Гик

    Данная загадка для новичков, проффи "вкурят" достаточно быстро.
    Я тут игрался с алгоритмом "пузырьковой" сортировки, получил такой глюк, благо найти его потребовалось чуть больше минуты НО он мне показался весьма поучительным и интересным, посему решил выложить код и дать возможность подумать другим кому любопытно.
    Попробуйте найти в нем ошибку и разобраться почему переменная len изменяет свое значение после вызова функции сортировки.
    Тут простой алгоритм "пузырьковой" сортировки.
    Код НЕ для ардуина.
    Можете если желаете скомпилировать его у себя и запустить в консоле.

    И так, код:
    Код (Text):

    #include <iostream>

    void sort( int *, int);
    void dislayArray( int *, int);

    int main( void )
    {
       using std::cout;
       using std::endl;
       int arr[] = {3214,12,3618,4381,1255,124,435,143,153,1213,41,172,3814,1245,2145};
       int len = ( sizeof( arr ) / sizeof( int ) );

       cout << "Array size before sort: " << len << endl;
       cout << "Array before:\t";
       dislayArray( arr, len );
       sort( arr, len );
       cout << "Array size after sort: " << len << endl;
       
       cout << "Array after:\t";
       dislayArray( arr, len );
       return 0;
    }

    void sort( int *arr, int size )
    {
       int in = 0;
       int out = 0;
       int temp = 0;
       
       for(out = size; out > 1; out--)
       {
         for( in = 0; in < size; in++ )
         {
           if( arr[ in ] > arr[ in + 1 ])
           {
             temp = arr[ in ];
             arr[ in ] = arr[ in + 1 ];
             arr[ in + 1 ] = temp;
           }
         }
       }
    }

    void dislayArray( int *arr, int size )
    {
       using std::cout;
       using std::endl;
       int i;
       for( i = 0; i < size; i++ )
       {
         cout << arr[ i ] << ' ';
       }
       cout << endl;
    }
     
    Вот что выводит программа:
    Код (Text):
    Array size before sort: 15
    Array before:  3214 12 3618 4381 1255 124 435 143 153 1213 41 172 3814 1245 2145
    Array size after sort: 4381
    Array after:  12 15 41 124 143 153 172 435 1213 1245 1255 2145 3214 3618 3814 4381 4201160 7876056 6 2130567168 0 0 2686824 4198585 1 3805120 3806864 -1 2686808 1959103701 1285187942 -2 .........................

    За многоточиями скрывается еще длинный набор цифр.

    Попробуйте понять почему вышло так:
    Array size before sort: 15
    Array size after sort: 4381

    :)
     
  2. Unixon

    Unixon Оракул Модератор

    С глюком все понятно, но спойлерить не буду. Вместо этого немного поругаюсь.

    С помощью этого кода вы выпустили в мир некоторое количество нечисти:
    1) использованием using;
    2) объявлением переменной цикла вне цикла без какой-либо на то необходимости;
    3) переменные цикла стилистически неотличимы от локальных переменных и аргументов функций;
    Постарайтесь обходиться без излишних сокращений, иначе в большом коде можете получить неприятные коллизии. В остальном все ОК. Ну еще неплохо было бы употребить const везде, где уместно.
     
  3. NR55RU

    NR55RU Гик

    Стилистика С++ для меня сейчас нова, пока только вырабатываю "правила хорошего тона" в С++.
    Привык в С, указывать переменную цикла вне цикла, так как компилятор при компиляции С ругался на объявления в цикле. Сейчас вот пытаюсь отвыкнуть, не всегда получается :)
    С using пока не определился как лучше, тот же Стивен Прата в своей книге рекомендует использовать using с "раскрытием" только требуемых функций и преподносит это как разумный подход, когда using используется лишь в функциях где он нужен и лишь с раскрытием только требуемых функций (using std::cout)
    В то же время вариант с использованием std::cout считается хорошим но для сокращения строки и улучшения читаемости опять таки рекомендуется способ выше, в общем согласно его совету плохо лишь так:
    using namespace std;
    Да еще если это "раскрывает" пространство std на все функции (весь файл).
    Так что тут пока я не "выработал" правильного подхода :)

    За критику большое спасибо, критику я люблю, тем более от намного более опытных людей, всегда читаю, всегда учитываю, всегда "мотаю на ус" :)
     
  4. Unixon

    Unixon Оракул Модератор

    Ну Пратт в принципе верно сказал про using, просто я более критичен. Смешением пространств имн можно дикие глюки поиметь, если там что-то начнет совпадать... Более того, иногда я упаковываю некоторые файлы в свои пространства имен, чтобы изолировать фрагменты кода от возможных перекрытий, особенно если там чисто процедурный подход использован.
     
  5. NR55RU

    NR55RU Гик

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

    По поводу объявления переменной цикла в инициализации цикла, вы совершенно правы, если она объявляется в инициализации цикле то её область видимости лишь цикл, если до инициализации цикла то область видимости вся функция в которой расположен цикл, что может вызвать проблемы если далее переменная с таким же именем будет использована без явного присвоения начального значения или же по опечатке и компилятор даже не заругается ведь такая переменная уже была объявлена.
    (Сие я разумеется написал не для вас, просто для новичков которые прочитают ваши замечания, дабы были более поняты причины замечания)

    Имена аргументов функций лично я обычно люблю предварять словом input ... inputSize, inputSpeed, inputWorker ... и тд. тп ... так название явно показывает о входящий данных.

    Так что лично я, всегда буду рад услышать ваше мнение или критику :)
     
  6. NR55RU

    NR55RU Гик

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

    И так в функции сортировки происходит беда беда.
    Длинна массива 15 но по факту конечная ячейка имеет значение 14.
    В условии цикла есть строчка i < size что дает нам крайнее значение i для массива 14 ... это и есть крайняя ячейка массива НО при перемещении используется конструкция arr[ in + 1 ] и вот тут то все и происходит.
    По сути выходит следующие arr[ 14 + 1 ] что дает нам значение 15-й ячейки которой у массива нет.

    Указатели это очень сильная сторона С/С++ но очень часто самые сильные стороны бывают и самыми слабыми.
    Компилятор никак не следит за выходом за границы массива и программа послушно записывается значение в память которая должна была бы принадлежать 15 ячейке массива... а эта память как раз отдана переменной длинны, таким образом в эту ячейку памяти попадает самое большое отсортированное значение из массива.

    Так, не явно была изменена совсем другая переменная к которой функция сортировки не имела доступа.
    Баги связанные с указателями считают одними из самых частых и порой одними из самых сложно отыскиваемых, посему будьте внимательны :)
     
  7. Unixon

    Unixon Оракул Модератор

    Не так...

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