On-line: гостей 0. Всего: 0 [подробнее..]
Программисты всех стран, объединяйтесь!

АвторСообщение



ссылка на сообщение  Отправлено: 27.09.14 17:53. Заголовок: Работа с динамической память, где ошибка?


Помогите пожалуйста понять задание и найти ошибку.

Было дано вот такое задание:


 цитата:
В этой задаче вам необходимо написать функцию getline, которая читает строку из стандартного потока ввода cin. Конец строки достигается, если прочитан символ '\n' или поток ввода прочитан полностью. Если прочитан символ '\n', то сохранять его в строку не нужно. Не забудьте, что строка должна оканчиваться нулевым символом. Всю выделенную динамически память, кроме результирующей строки, необходимо освободить - будьте внимательны! Указатель возвращенный из getline будет освобожден с помощью delete[].
Замечания:

  • выделяйте и освобождайте память в стиле C++,
  • функция ничего не должна выводить (Sample Output в примере — это возвращаемое значении функции в формате длина возвращаемой строки:возвращаемая строка, длину в строке возвращать не нужно - эта информация для сведения).




  • На лекции было рассказано как выделять память в с и с++ стиле, и про ссылки с указателями.
    Полученное задание меня обездвижило на минут 30)

    Почитав коменты к заданию более менее стало понятно что делать.
    Получился вот такой нерабочий код №1:

    char *getline() { 
    char ch = 0; //переменная для хранения вводимого символа
    char *ch2 = new char[90000]; //выделения памяти условно большого размера для записывания ввода
    int count = 0; //счётчик для определения нужного размера памяти
    while (cin.get(ch) && ch != '\n'){ //начало цикла для посимвольного ввода, который оборвётся когда увидит \n и ничего не вернёт
    ch2[count] = ch; //переписывание в память вводимых символов
    count++;


    if (ch == '\0'){ //когда закончен ввод приступаем ко второй фазе функции
    char *ch3 = new char[count-1]; //выделяем нужное кол-во памяти под символы
    count = 0; //сбрасываем счётчик
    while(ch2){ // начинаем переписывать символы в память нужного размера
    ch2++; //шагаем по указателю массива с символами
    count++;
    ch3[count] = *ch2; //переписывание символов
    }
    ch3[count+1] = '\0'; //добавляем в конец символ окончания строки
    delete [] ch2; //очищаем память
    return ch3; //возвращаем массив заполненный символами нужной длинны.
    }


    }
    }


    Но этот код в тесте выдавал runtime error.

    В попытках всё переосмыслить был рождён другой нерабочий код №2:
     
    char *getline() {
    char *ch = new char[90000]; //выделяем условно большой кусок памяти
    while (cin.get(*ch)){ //принимаем вводимую строку

    int count = 0; //создаём счётчик для подсчёта длинны строки

    while(*ch && *ch != '\n'){ //проходимся по строке через адрес и считаем её длинну
    ch++;
    count++;
    }
    ch; //сбрасываем адрес
    char *ch2 = new char[count]; //создаём массив нужного размера
    while(*ch && *ch != '\n'){ //опять проходимся по строке через адрес и сохраняем её значение в новый массив
    *ch2 = *ch;
    ch++;
    ch2++;
    }
    delete [] ch; //очищаем память
    ch2[count] = '\0'; //добавляем символ конца строки
    return ch2; //возвращаем строку в памяти нужного размера



    }
    }

    Этот код вообще выдаёт вот такую ошибку: *** Error in `./untitled': munmap_chunk(): invalid pointer: 0x00000000008f2011 ***

    Предполагаю, что у меня где-то серьёзная поломка мозга и абсолютное непонимание задания...

    И объясните пожалуйста каким образом будет работать while(cin.get(ch)) к примеру.
    Вот я запускаю программу, программа ожидает ввода. Я ввожу некоторые символы и нажимаю ентер. Это получается, что в ch будет содержаться массив с этими символами и цикл запустится 1 раз? Или будет происходить цикл каждый когда я нажимаю на клаве символ? Или произойдёт срабатывание цикла столько раз, сколько я ввёл символов?

    Коментарии, которые возможно помогут понять суть задания(хотя мне не особо помогли):
  • Коментарий от преподавателя:
    Скрытый текст


  • Коментарий от одного из учащихся:
    Скрытый текст


    Форматирования кода тут не хватает( Не нашёл как тут это сделать.

  • Спасибо: 0 
    ПрофильЦитата Ответить
    Ответов - 10 [только новые]





    ссылка на сообщение  Отправлено: 27.09.14 20:28. Заголовок: Чтобы код выглядел ф..


    Чтобы код выглядел форматированным, его надо заключить в
    тег pre2 (начальный и конечный теги).

    Ваша первая функция имеет неопределенное поведение. Она ничего не возвращает
    в случае, если был обнаружен конец потока или встречен
    символ новой строки ('\n').

    Вот структура вашей функции

     
    char *getline() {
    //...
    while (cin.get(ch) && ch != '\n'){ //начало цикла для посимвольного ввода, который оборвётся когда увидит \n и ничего не вернёт
    //...
    if (ch == '\0'){
    //...
    while(ch2){
    //...
    }
    //...
    }
    }
    }


    Вы должны создавать буфер для возвращаемой строки после выполнения цикла
    и возвращать его из функции.

    также в цикле while вы должны включить условие, что исходный
    буфер не переполнен.

    И лучше не выделять такой огромный буфер. Достаточно выделить, например, 100 байтов.
    Если при очередном вводе символа буфер будет заполнен, то выделять новый буфер
    с увеличенным размером и переписывать туда текущий буфер. То есть каждый раз
    при заполнении текущего буфера выделять новый буфер на 100 байтов больше, чем
    размер предыдущего буфера.

    Вторая реализация функции также имеет неопределенное повделение. Например,
    в этом цикле
     
    while(*ch && *ch != '\n'){ //проходимся по строке через адрес и считаем её длинну
    ch++;
    count++;
    }


    вы увеличиваете постоянно указатель ch, пока не встретится нулевой символ или
    символ новой строки. Так как память, выделенная под буфер, была не инициализирована,
    то мы имеем дело со случайным процессом. Более того сам этот цикл не имеет смысла,
    так как вы ничего не считываете в буфер внутри этого цикла.


    Спасибо: 0 
    ПрофильЦитата Ответить



    ссылка на сообщение  Отправлено: 27.09.14 21:21. Заголовок: Сыроежка пишет: И л..


    Сыроежка пишет:

     цитата:
    И лучше не выделять такой огромный буфер. Достаточно выделить, например, 100 байтов.
    Если при очередном вводе символа буфер будет заполнен, то выделять новый буфер
    с увеличенным размером и переписывать туда текущий буфер. То есть каждый раз
    при заполнении текущего буфера выделять новый буфер на 100 байтов больше, чем
    размер предыдущего буфера.



    А как это сделать? Это надо будет пройтись циклом по текущему буферу и переписать в новый буфер все значения?
    Пытался эксперементировать и не могу понять почему этот код работает?
    И как узнать что буфер полон? sizeof возвращает размер указателя, а не объекта(
     
    int *a = new int[10];
    int *b = new int[20];
    a[3] = 3;
    b = a;
    delete [] a;
    cout << a[3] << endl;

    Выводится 3.



    Спасибо: 0 
    ПрофильЦитата Ответить



    ссылка на сообщение  Отправлено: 27.09.14 21:27. Заголовок: evsign пишет: И как..


    evsign пишет:

     цитата:
    И как узнать что буфер полон? sizeof возвращает размер указателя, а не объекта(


    Или это надо счётчик делать?

    Спасибо: 0 
    ПрофильЦитата Ответить



    ссылка на сообщение  Отправлено: 27.09.14 21:32. Заголовок: Отследить переполнен..


    Отследить переполнение буфера очень просто, так как вам известен его размер. Достаточно, например,
    написать

     
    while ( cin.get( ch ) && ch != '\n' )
    {
    if ( ++count > N )
    {
    // выделяем новый буфер и переписываем в него содержимое текущего буфера
    // ...


    где N - это размер текущего буфера.

    Что касается этого кода
     
    int *a = new int[10];
    int *b = new int[20];
    a[3] = 3;
    b = a;
    delete [] a;
    cout << a[3] << endl;


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



    Спасибо: 0 
    ПрофильЦитата Ответить



    ссылка на сообщение  Отправлено: 27.09.14 21:59. Заголовок: На данный момент име..


    На данный момент имею такой код. Остаётся неясным момент с созданием нового буфера
     
    char *getline() {
    int n = 100; //размер буфера
    int count = 0;
    char *ch = new char[n]; //создания буфера
    while (cin.get(ch) && ch != '\n'){ //начало цикла для посимвольного ввода, который оборвётся когда увидит \n и ничего не вернёт
    if(++count == n){ //проверяем переполнение буфера
    n += 100; //увеличиваем размер буфера
    char *ch2 = new char[n]; //выделяем новый буфер
    while(ch){ //переписываем старый буфер в новый буфер
    *ch2 = *ch;
    ch++;
    ch2++;
    }
    }
    if (ch == '\0'){ //когда встречается конец строки
    delete [] ch; //удаляем старый буфер
    return ch2; //возвращаем новый буфер
    }
    }
    }


    Тут получается, если к примеру буфер превысит 200 байт, то код будет неработоспособным...


    Спасибо: 0 
    ПрофильЦитата Ответить



    ссылка на сообщение  Отправлено: 27.09.14 22:04. Заголовок: И правильно ли я пер..


    И правильно ли я переписываю старый буфер в новый?

    Спасибо: 0 
    ПрофильЦитата Ответить



    ссылка на сообщение  Отправлено: 28.09.14 07:28. Заголовок: Ваша функция по-преж..


    Ваша функция по-прежнему ничего не возвращает после выполнения внешнего цикла while.
    То есть она имеет неопределенное поведение.

    Копирование буферов можно сделать с помощью стандартной C функции memcpy.

    Вот как может выглядеть ваша фугкция

     
    #include <iostream>
    #include <cstring>

    char * getline()
    {
    const size_t INITIAL_SIZE = 100; //размер буфера
    const size_t ALLOCATION_STEP = 10;

    char *buffer = new char[INITIAL_SIZE]; //создания буфера
    size_t current_size = INITIAL_SIZE;

    size_t n = 0;
    char c;

    while ( std::cin.get( c ) && c != '\n' )
    {
    if ( n + 1 > current_size )
    {
    current_size += ALLOCATION_STEP; //увеличиваем размер буфера
    char *p = new char[current_size]; //выделяем новый буфер
    memcpy( p, buffer, n );
    delete [] buffer;
    buffer = p;
    }

    buffer[n++] = c;
    }

    char *s = new char[n + 1];
    memcpy( s, buffer, n );
    s[n] = '\0';

    delete [] buffer;

    return s;
    }

    int main()
    {
    char *s = getline();

    std::cout << std::strlen( s ) << ": \"" << s << "\"\n";

    delete [] s;

    return 0;
    }


    Если ввести строку Hello, World!. то вывод на консоль будет
     
    13: "Hello, World!"


    Возможно, она содержит какие-либо опечатки (я ее не проверял),
    но как исходный шаблон кода вполне может быть использована.



    Спасибо: 0 
    ПрофильЦитата Ответить



    ссылка на сообщение  Отправлено: 28.09.14 15:33. Заголовок: Спасибо большое) Не..


    Спасибо большое)

    Не могли бы вы проккоментировать вот этот момент? Не совсем понимаю, что тут происходит.
    buffer = p;

    И ещё такой момент. Не могу понять.
    Вот допустим происходит первое переполнение буфера, срабатывает if где происходит создание нового с увеличенным размером на 10 байт, т.е. получается новый буфер со всей инфой p - 110байт. А что будет, когда размер буфера опять переполнится?
    Мы же опять попадём в условие, где попытаемся выделить увеличенный буфер с тем же названием и удалить уже несуществующий.

    Ещё раз спасибо что со мной возитесь)

    Спасибо: 0 
    ПрофильЦитата Ответить



    ссылка на сообщение  Отправлено: 28.09.14 17:31. Заголовок: buffer и p - это ука..


    buffer и p - это указатели, то есть переменные, которые хранят адреса.
    После того, как память, адрес которой хранился в buffer, была удалена,
    мы присваиваем адрес новой выделенной памяти этой переменной. То есть
    переменная buffer всегда будет хранить адрес текущей выделенной памяти.

    У выделенной памяти нет имени. Мы лишь храним ее адрес в указателях.
    Когда надо выделить новый участок памяти, мы его адрес сохраняем в локальной
    переменной блока предложения if с именем p. Освобождаем память,
    адрес которой хранился в buffer и после ее освобождения (ее адрес нам уже
    не нужен) переменной buffer присваиваем адрес нового участка памяти, который
    мы.сохранили в переменной p



    Спасибо: 1 
    ПрофильЦитата Ответить



    ссылка на сообщение  Отправлено: 28.09.14 21:46. Заголовок: Огромное спасибо, вс..


    Огромное спасибо, всё понял)

    Спасибо: 0 
    ПрофильЦитата Ответить
    Ответ:
    1 2 3 4 5 6 7 8 9
    большой шрифт малый шрифт надстрочный подстрочный заголовок большой заголовок видео с youtube.com картинка из интернета картинка с компьютера ссылка файл с компьютера русская клавиатура транслитератор  цитата  кавычки моноширинный шрифт моноширинный шрифт горизонтальная линия отступ точка LI бегущая строка оффтопик свернутый текст

    показывать это сообщение только модераторам
    не делать ссылки активными
    Имя, пароль:      зарегистрироваться    
    Тему читают:
    - участник сейчас на форуме
    - участник вне форума
    Все даты в формате GMT  3 час. Хитов сегодня: 3
    Права: смайлы да, картинки да, шрифты да, голосования нет
    аватары да, автозамена ссылок вкл, премодерация откл, правка нет