Нарушение доступа с помощью указателей? - C ++ - PullRequest
0 голосов
/ 08 февраля 2010

Я написал простую программу токенизации строк с использованием указателей для недавнего школьного проекта. Однако у меня возникли проблемы с моим StringTokenizer::Next() методом, который при вызове должен возвращать указатель на первую букву следующего слова в массиве char. Я не получаю ошибок во время компиляции, но я получаю ошибку во время выполнения, которая гласит:

Unhandled exception at 0x012c240f in Project 5.exe: 0xC0000005: Access violation reading location 0x002b0000.

Программа в настоящее время токенизирует массив char, но затем останавливается, и появляется эта ошибка. У меня есть ощущение, что это связано с проверкой NULL, которую я делаю в моем методе Next().

Так как я могу это исправить?

Кроме того, если вы заметили что-нибудь, что я мог бы сделать более эффективно или с лучшей практикой, пожалуйста, дайте мне знать.

Спасибо !!


StringTokenizer.h:

#pragma once

class StringTokenizer
{
public:
StringTokenizer(void);
StringTokenizer(char* const, char);
char* Next(void);
~StringTokenizer(void);
private:
char* pStart;
char* pNextWord;
char delim;
};

StringTokenizer.cpp:

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

StringTokenizer::StringTokenizer(void)
{
pStart = NULL;
pNextWord = NULL;
delim = 'n';
}

StringTokenizer::StringTokenizer(char* const pArray, char d)
{
pStart = pArray;
delim = d;
}

char* StringTokenizer::Next(void)
{
pNextWord = pStart;
if (pStart == NULL) { return NULL; }

while (*pStart != delim) // access violation error here
{
    pStart++;
}

if (pStart == NULL) { return NULL; }

*pStart = '\0'; // sometimes the access violation error occurs here
pStart++;

return pNextWord;
}

StringTokenizer::~StringTokenizer(void)
{
delete pStart;
delete pNextWord;
}

main.cpp:

// The PrintHeader function prints out my
// student info in header form
// Parameters - none
// Pre-conditions - none
// Post-conditions - none
// Returns - void
void PrintHeader();

int main ( )
{
const int CHAR_ARRAY_CAPACITY = 128;
const int CHAR_ARRAY_CAPCITY_MINUS_ONE = 127;

// create a place to hold the user's input
// and a char pointer to use with the next( ) function
char words[CHAR_ARRAY_CAPACITY];
char* nextWord;

PrintHeader();

cout << "\nString Tokenizer Project";
cout << "\nyour name\n\n";
cout << "Enter in a short string of words:";
cin.getline ( words, CHAR_ARRAY_CAPCITY_MINUS_ONE );

// create a tokenizer object, pass in the char array
// and a space character for the delimiter
StringTokenizer tk( words, ' ' );

// this loop will display the tokens
while ( ( nextWord = tk.Next ( ) ) != NULL )
{
    cout << nextWord << endl;
}


system("PAUSE");
return 0;
}


EDIT:

Хорошо, у меня теперь программа работает нормально, если в качестве разделителя используется пробел. Но если я передам его как `/ 'в качестве разделителя, он снова обнаружит ошибку нарушения доступа. Есть идеи?

Функция, которая работает с пробелами:

char* StringTokenizer::Next(void)
{
pNextWord = pStart;

if (*pStart == '\0') { return NULL; }

while (*pStart != delim)
{
    pStart++;
}

if (*pStart = '\0') { return NULL; }

*pStart = '\0';
pStart++;

return pNextWord;
}

Ответы [ 4 ]

4 голосов
/ 08 февраля 2010

Нарушение доступа (или «ошибка сегментации» в некоторых ОС) означает, что вы пытались читать или записывать в место в памяти, которое вы никогда не выделяли.

Рассмотрим цикл while в Next ():

while (*pStart != delim) // access violation error here
{
    pStart++;
}

Скажем, строка "blah\0". Обратите внимание, что я добавил завершающий ноль. Теперь спросите себя: как этот цикл узнает, что останавливается, когда достигает конца строки?

Что еще более важно: что произойдет с *pStart, если цикл не сможет остановиться в конце строки?

1 голос
/ 08 февраля 2010

Этот ответ предоставляется на основе отредактированного вопроса и различных комментариев / наблюдений в других ответах ...

Во-первых, каковы возможные состояния для pStart при вызове Next ()?

  1. pStart имеет значение NULL (конструктор по умолчанию или иначе установлен в NULL)
  2. * pStart равно '\ 0' (пустая строка в конце строки)
  3. *pStart - это delim (пустая строка в соседнем разделителе)
  4. * pStart - это что-то еще (токен непустой строки)

На данный момент нам нужно беспокоиться только о первомвариант.Поэтому я бы использовал здесь оригинальную проверку «если»:

if (pStart == NULL) { return NULL; }

Почему бы нам не беспокоиться о случаях 2 или 3?Возможно, вы захотите рассматривать соседние разделители как имеющие токен пустой строки между ними, в том числе в начале и конце строки.(Если нет, настройте по вкусу.) Цикл while будет обрабатывать это для нас, при условии, что вы также добавите проверку '\ 0' (необходимо независимо):

while (*pStart != delim && *pStart != '\0')

После того, как цикл while находится там, где вам нужнобыть осторожным.Каковы возможные состояния сейчас?

  1. * pStart равен '\ 0' (токен заканчивается в конце строки)
  2. * pStart является разделителем (токен заканчивается следующим разделителем)

Обратите внимание, что сам pStart здесь не может быть НЕДЕЙСТВИТЕЛЕН.

Вам необходимо вернуть pNextWord (текущий токен) для обоих этих условий, чтобы не отбрасывать последнеетокен (т. е. когда * pStart равен '\ 0').Код обрабатывает случай 2 правильно, но не случай 1 (исходный код опасно увеличился на pStart после '\ 0', новый код возвратил NULL).Кроме того, важно правильно сбросить pStart для случая 1, чтобы следующий вызов Next () возвратил NULL.Я оставлю точный код в качестве упражнения для читателя, так как в конце концов это домашняя работа;)

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

Наконец, я заметил, что в вашем деструкторе есть вызовы delete для pStart и pNextWord.Во-первых, для удаления массивов вам нужно использовать delete [] ptr; (т.е. удаление массивов).Во-вторых, вы не удалили бы и pStart, и pNextWord, потому что pNextWord указывает на массив pStart.В-третьих, к концу pStart больше не указывает на начало памяти, поэтому вам потребуется отдельный элемент для хранения исходного начала для вызова delete [].Наконец, эти массивы размещаются в стеке, а не в куче (т. Е. С использованием char var[], а не char* var = new char[]), и, следовательно, их не следует удалять.Поэтому вам следует просто использовать пустой деструктор.

Еще один полезный совет - подсчитать количество вызовов new и delete;там должно быть одинаковое количество каждого.В этом случае у вас ноль new вызовов и два delete вызовов, что указывает на серьезную проблему.Если бы это было наоборот, это указало бы на утечку памяти.

1 голос
/ 08 февраля 2010

Inside :: Далее вам нужно проверить наличие символа разделителя, но вам также нужно проверить конец буфера (который, как я предполагаю, обозначен \ 0).

while (*pStart != '\0' && *pStart != delim) // access violation error here
{
    pStart++;
}

И я думаю, что эти тесты в :: Далее

if (pStart == NULL) { return NULL; }

Должно быть так.

if (*pStart == '\0') { return NULL; }

То есть вы должны проверять Nul-символ, а не нулевой указатель. Неясно, намереваетесь ли вы в этих тестах обнаружить неинициализированный указатель pStart или конец буфера.

0 голосов
/ 08 февраля 2010

Нарушение доступа обычно означает неверный указатель.

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

...