Модераторы: bsa
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Нарушение доступа к памяти при вызове деструкторов, данные-члены в дин. памяти 
V
    Опции темы
Курсант
Дата 4.6.2009, 12:23 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 338
Регистрация: 21.2.2009
Где: Балашиха или Воро неж

Репутация: 1
Всего: 4



Трабла такова. При освобождении памяти, выделенной под данные-члены, появляется ошибка "CRT detected that the application wrote to memory after end of heap buffer".. И так по количеству данных-членов (строк)... Сколько строк столько и сообщений об ошибке.

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

Собственно история smile
Написал модуль класса WordCountList, для подсчета слов. У него есть метод void Put (char* слово), и в зависимости от того есть такое слово в списке или нет, объект либо увеличивает счетчик для данного слова, либо создает новую запись с единичным счетчиком.
Собственно, реализация класса:

Код

// ** Модуль класса WordCounterList .....
#include <iostream>
using namespace std;

class WordCounter
{
public:
    WordCounter(const char* const);
    ~WordCounter() {delete[] itsString;} // ******************* Вот из за этого delete [] выскакивает ошибка...
    void Inc () {itsCount++;}
    char* GetString() {return itsString;}
    unsigned short GetCount() {return itsCount;}
    char* itsString;
private:
    unsigned short itsCount;
};

WordCounter::WordCounter(const char* const theWord)
: itsCount(1)
{
    int len = strlen(theWord);
    itsString = new char[len];
    for( int i=0; i < len ; itsString[i]=theWord[i], i++);
    itsString[len]='\0';
}

class WordNote : public WordCounter
{
public:
    WordNote(const char* const);
    ~WordNote() { delete itsNext; itsNext = 0;}
    WordNote* GetNext() {return itsNext;}
    void SetNext(WordNote* theNote) {itsNext = theNote;}
private:
    WordNote* itsNext;
};

WordNote::WordNote(const char* const theWord):
WordCounter(theWord),
itsNext(0)
{
}

class WordCounterList
{
public:
    WordCounterList(): itsBaseNote(0), itsLastNote(0) {};
    ~WordCounterList() {delete itsBaseNote; itsBaseNote=0;}
    void Put(const char* const);
    void Print (ostream&);
private:
    WordNote* itsBaseNote;
    WordNote* itsLastNote;
};

void WordCounterList::Put (const char* const theString)
{
    WordNote* tempNote;
    tempNote = itsBaseNote;
    bool finded = false;
    while (tempNote)
    {
        // для каждой записи списка сравниваем аргумент (слово) с содержимым;
        bool equal = true;
        // для начала сравниваем длины слов...
        if ( strlen(tempNote->itsString) == strlen(theString))
        {
            // если равны то посимвольно сравниваем слова...
            for (unsigned short i=0; i < strlen(theString); i++)
                if ( tempNote->itsString[i] != theString[i])
                {
                    equal = false;
                    break;
                }
        }
        else equal = false;
        if (equal)
        {
            tempNote->Inc();
            finded = true;
            break;
        }
        // устанавливаем признак того что слово найдено в списке
        tempNote = tempNote->GetNext();
    }
    // если не найдено объекта счетчика с таким же словом то...
    if (!finded)
    {
        // создаем в дин памяти новый объект связанного счетчика
        tempNote = new WordNote(theString);
        // если в списке ничего нет, то
        if (!itsBaseNote)
        {
            // устанавливаем созданный эл-т в качестве базового и конечного...
            itsBaseNote = tempNote;
        }
        else
        {
            // в противном случае прикрепляем к хвосту новый элемент...
            itsLastNote->SetNext(tempNote);
            // и заносим его в указатель LastNote...
        }
        itsLastNote = tempNote;
    }
    tempNote = 0;
}

void WordCounterList::Print(ostream& stream)
{
    WordNote* tempNote;
    tempNote = itsBaseNote;
    while (tempNote)
    {
        stream << "\"" << tempNote->GetString() << "\" встречается " << tempNote->GetCount() << " раз(а)." << endl;
        tempNote = tempNote->GetNext();
    }
    tempNote = 0;
}


Этот объект используется в программе таким образом:

Код

#include <iostream>
#include <fstream>
#include "WordsCounterList.h"
using namespace std;

// функция преобразования символа ASCII из верхнего регистра в нижний
char ToLowerCase (char ch)
{
    if (((ch >= 'A') && (ch <= 'Z')) || (( ch >= 'А') && (ch <= 'Я')))
        ch = ch + 32;
    if (ch == 'Ё')
        ch = 'ё';
    return ch;
}

// функция проверки, является ли символ ASCII алфавитно-цифровым.
bool isAlphaNum (char ch)
{
   if (((ch >= 'A') && (ch <= 'Z')) || (( ch >= 'a') && (ch <= 'z')) || ((ch >= 'А') && (ch <= 'я')) || (ch=='Ё') || (ch == 'ё') || ((ch > '0') && (ch < '9')))
       return true;
   return false;
}

// функция очистки полученного слова от неалфавитно-цифр. символов вначале и в конце.
char* ProceedString (char* String)
{
    int start(0), end(strlen(String));
    for ( ; end >= 0, !isAlphaNum(String[end]); String[end]='\0', end--);
    for ( ; start < end, (!isAlphaNum(String[start])&& ( String[start]!='\0')); start++);
    for ( ; start <=end ; String[end]=ToLowerCase(String[end]), end--);
    return String+start;
}


int main()
{
    // объявляем объект списка счечиков слов
    WordCounterList WCList;
    char * theWord = new char[80];
    cout << "Enter the input file name: ";
    cin >> theWord;
    ifstream fin (theWord);
    // до конца файла читаем из него слова, разделенные пробелами
    while (!fin.eof())
    {
        fin >> theWord;
        theWord = ProceedString (theWord);
        if (strlen(theWord))
        {
            WCList.Put(theWord);
        }
    }
    fin.close();
    cout << "Enter the output file name: ";
    cin >> theWord;
    ofstream fout (theWord);
    // вызываем метод, который печатает статистику слов в поток вывода, в качестве потока даем ему поток fout...
    WCList.Print(fout);
    fout.close();
    cout << "Ok" << endl;
    return 0;
    // ***************вот здесь начинаются траблы, когда происходит разрушение объектов**************
}


Собственно все... Спасибо smile
PM ICQ Skype   Вверх
mes
Дата 4.6.2009, 12:33 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


любитель
****


Профиль
Группа: Участник Клуба
Сообщений: 7954
Регистрация: 14.1.2006

Репутация: 79
Всего: 250



весь код не смотрел, но отсутсвие копи-конструктора наводит  на подозрения. И если где то в программе есть передача подобного объекта по значению, то
оба объект (оригинал и копия) будут указывать на один и тот же выделенный блок памяти (itsString), а следовательно при удалении одного из этих объектов, указатель(itsString), становится не валидным, что ведет к ошибке при любом его дальнейшем использовании, в том числе и повторное delete[] itsString.


--------------------
PM MAIL WWW   Вверх
gosn1ck
Дата 4.6.2009, 13:40 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Шустрый
*


Профиль
Группа: Участник
Сообщений: 103
Регистрация: 18.4.2009

Репутация: 1
Всего: 1



простите, что вмешиваюсь, а что такое копи-конструктор ?))
PM MAIL ICQ   Вверх
mes
Дата 4.6.2009, 13:42 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


любитель
****


Профиль
Группа: Участник Клуба
Сообщений: 7954
Регистрация: 14.1.2006

Репутация: 79
Всего: 250



Цитата(gosn1ck @  4.6.2009,  12:40 Найти цитируемый пост)
простите, что вмешиваюсь, а что такое копи-конструктор ?)) 

Код

class T
{
   T();
   T(int);
   T(const T&); //<-- этот 
};

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


Это сообщение отредактировал(а) mes - 4.6.2009, 13:44


--------------------
PM MAIL WWW   Вверх
azesmcar
Дата 4.6.2009, 13:56 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 52
Всего: 211



Цитата(gosn1ck @  4.6.2009,  13:40 Найти цитируемый пост)
простите, что вмешиваюсь, а что такое копи-конструктор ?)) 

copy constructor, он же копирующий конструктор - это конструктор который принимает в качестве аргумента ссылку или константную ссылку на обьект своего типа.
Код

class A
{
public:
   A(const A&); //copy constructor
   A(A&); //copy constructor
}

вызывается эта прелесть в этом случае
Код

A t;
A v1 = t; //копи конструктор
A v2(t);  //копи конструктор

еще он вызывается при других видах копирования, например при передаче параметра функции по значению.
Код

class B
{
   A t;
public:
   B() {
       t = /*...*/; //это не вызов копи конструктора, это оператор присваивания
   }
}

для вызова копи конструктора в данном случае существует список инициализации
Код

class B
{
   A t;
public:
   B() : t(/*объект*/) //вот вызов копи конструктора
   {
   }
}

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

class A
{
   int i;
   int j;
public:
    A(const A& r): i(r.i), j(r.j) {}
}

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

class A
{
private:
   A(const A&);
public:
   /* ... */
};

надеюсь ничего важного не упустил
думаю достаточно информации на первое время smile


Это сообщение отредактировал(а) azesmcar - 9.4.2010, 08:24
PM   Вверх
Курсант
Дата 4.6.2009, 14:35 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 338
Регистрация: 21.2.2009
Где: Балашиха или Воро неж

Репутация: 1
Всего: 4



В том то и дело что по значению я не передавал объекты - все завязано на указателях... Дубль два smile Я подошел к какой-то суровой правде жизни smile

Ошибка пропала после того как в конструкторе счетчика объявляем массив символов на единицу больший strlen(аргумента) ... То есть конфликтовали у меня два участка кода: этот
Код

WordCounter::WordCounter(const char* const theWord)
: itsCount(1)
{
    int len = strlen(theWord); // получается массив недостаточного размера чтобы хранить слово и нуль-символ в конце...
    itsString = new char[len]; 
    for( int i=0; i < len ; itsString[i]=theWord[i], i++);
    itsString[len]='\0'; // запись за пределы массива, т.к. крайний символ есть itsString[len-1];
}


и этот:

Код

~WordCounter() {delete[] itsString;}


Получается что itsString[len]='\0' в первом участке обращается за пределы массива строк... но вот незадача... Ошибки доступа к памяти не возникает - просто затирается видимо что-то, либо промежутки между данными специально оставлены, не суть важно...

Важно то, что если убрать delete[] itsString, либо если правильно организовать работу с массивом (то есть объявить его на единицу больше), то ошибки не появляется... Отсюда вопрос... Ошибка возникает из за особенностей учета памяти, то есть delete[] itsString пытается удалить не только тот участок памяти, который был выделен изначально, но и тот байт за пределами массива, в который я писал когда неправильно организовал работу с массивом... Вопрос - как такое вообще возможно? ИМХО это нереально, но опыт подтверждает.

приложение А: исправленный конструктор
Код

WordCounter::WordCounter(const char* const theWord)
: itsCount(1)
{
    int len = strlen(theWord)+1;
    itsString = new char[len];
    for( int i=0; i < len-1 ; itsString[i]=theWord[i], i++);
    itsString[len-1]='\0';
}


PM ICQ Skype   Вверх
azesmcar
Дата 4.6.2009, 14:36 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


uploading...
****


Профиль
Группа: Участник Клуба
Сообщений: 6291
Регистрация: 12.11.2004
Где: Армения

Репутация: 52
Всего: 211



Курсант

Код

int len = strlen(theWord);
itsString = new char[len]; 
for( int i=0; i < len ; itsString[i]=theWord[i], i++);
itsString[len]='\0';//тут явная ошибка - выход за рамки массива

strlen возвращает вам не размер массива а длину строки. Не забывайте про завершающий \0

Добавлено @ 14:37
Курсант

немного опоздал с ответом smile

Это сообщение отредактировал(а) azesmcar - 4.6.2009, 21:01
PM   Вверх
Курсант
Дата 4.6.2009, 14:52 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 338
Регистрация: 21.2.2009
Где: Балашиха или Воро неж

Репутация: 1
Всего: 4



Спасибо smile И все ж таки вопрос открыт остается - если я пишу по ошибке не туда, куда надо, то почему потом деструктор начинает удалять то, что массиву не принадлежит? Получается что ассоциированный с указателем размер памяти зависит не только от объявления, но и от того, к какой памяти я через этот указатель обращаюсь? весьма странно, и очень непонятно как это реализованно...
PM ICQ Skype   Вверх
xvr
Дата 4.6.2009, 15:19 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Комодератор
Сообщений: 7046
Регистрация: 28.8.2007
Где: Дублин, Ирландия

Репутация: 35
Всего: 223



Цитата(Курсант @ 4.6.2009,  14:52)
Спасибо smile И все ж таки вопрос открыт остается - если я пишу по ошибке не туда, куда надо, то почему потом деструктор начинает удалять то, что массиву не принадлежит?

Он то удаляет то, что ему принадлежит, а вот вы своим '\0' в конце строки стерли данные, которые вам не принадлежали. А принадлежали они менеджеру динамической памяти, о чем он и сказал, когда появилась такая возможность (на delete)
Цитата

Получается что ассоциированный с указателем размер памяти зависит не только от объявления, но и от того, к какой памяти я через этот указатель обращаюсь?
С указателем никакие размеры не ассоциируются. Менеджер памяти использует свои собственные механизмы для управления этой памятью. К сожалению он не может защитить свои структуры данных, управляющие кучей, от случайного вмешательства пользователя.
Когда вы заказываете память определенного размера, то менеджер памяти возвращает вам указатель на выделенную память, при этом гарантируется, что вы можете свободно обращаться через него в пределах того размера, который указали при заказе памяти. ЗА ПРЕДЕЛЫ этого размера вы обращаться НЕ ИМЕЕТЕ ПРАВА вообще. Последствия будут непредсказуемые

PM MAIL   Вверх
Курсант
Дата 4.6.2009, 15:30 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 338
Регистрация: 21.2.2009
Где: Балашиха или Воро неж

Репутация: 1
Всего: 4



Цитата(xvr @ 4.6.2009,  15:19)
С указателем никакие размеры не ассоциируются. Менеджер памяти использует свои собственные механизмы для управления этой памятью...

Да, Вы правы, только что попробовал освободить два раза один указатель - эффекты возникают более красочные smile
PM ICQ Skype   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "C/C++: Для новичков"
JackYF
bsa

Запрещается!

1. Публиковать ссылки на вскрытые компоненты

2. Обсуждать взлом компонентов и делиться вскрытыми компонентами

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


Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, JackYF, bsa.

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | C/C++: Для новичков | Следующая тема »


 




[ Время генерации скрипта: 0.0559 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.