Pergunta

Eu escrevi um programa de corda tokenizing simples usando ponteiros para um projeto da escola recente. No entanto, estou tendo problemas com meu método StringTokenizer::Next(), que, quando chamado, deve retornar um ponteiro para a primeira letra da palavra seguinte na matriz de char. Eu recebo nenhum erro de tempo de compilação, mas eu recebo um erro de execução que estados:

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

O programa atualmente tokenizes a matriz char, mas depois pára e este erro aparece. Tenho a sensação de que tem a ver com o NULL verificando que eu estou fazendo no meu método Next().

Então, como posso corrigir isso?

Além disso, se você notar qualquer coisa que eu poderia fazer de forma mais eficiente ou com a prática melhor, por favor me avise.

Thanks !!


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:

Ok, eu tenho a multa trabalhando programa agora, desde que o delimitador é um espaço. Mas se eu passar um '/' como um delimitador, ele vem com o erro violação de acesso novamente. Alguma idéia?

função que funciona com espaços:

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;
}
Foi útil?

Solução

Esta resposta é dada com base na pergunta editada e vários comentários / observações em outras respostas ...

Em primeiro lugar, quais são os estados possíveis para PStart quando Next () é chamado?

  1. PStart é NULL (construtor padrão ou de outra forma definido para NULL)
  2. * PStart é '\ 0' (string vazia no final da string)
  3. * Pinicial é delimitador (cadeia vazio em um delimitador adjacente)
  4. * PStart é qualquer outra coisa (símbolo não-empty-string)

Neste ponto, só precisa se preocupar com a primeira opção. Portanto, gostaria de usar o original "se" check aqui:

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

Por que nós não precisa se preocupar com casos 2 ou 3 ainda? Você provavelmente vai querer tratar delimitadores adjacentes como tendo um vazio-cadeia do token entre eles, incluindo no início e no final da cadeia. (Se não, ajuste a gosto.) O loop while irá lidar com isso para nós, desde que você também adicionar o '\ 0' cheque (necessária independentemente):

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

Depois que o loop while é onde você precisa ser cuidadoso. Quais são os possíveis estados agora?

  1. * PStart é '\ 0' (extremidades de token no fim do string)
  2. * PStart é delim (extremidades simbólicos no próximo delimitador)

Note que se PStart não pode ser NULL aqui.

Você precisa voltar pNextWord (token atual) para ambos destas condições para que você não deixe cair o último token (ou seja, quando * PStart é '\ 0'). O caso alças de código 2 corretamente, mas não case 1 (código original perigosamente incrementado PStart passado '\ 0', o novo código NULL retornado). Além disso, é importante para repor PStart para o caso 1 corretamente, de modo que a próxima chamada para next () retorna NULL. Vou deixar o código exato como um exercício para leitor, uma vez que é dever de casa depois de tudo;)

É um bom exercício para delinear os possíveis estados de dados ao longo de uma função, a fim de determinar a ação correta para cada estado, semelhante ao formalmente definir casos base vs. casos recursiva para funções recursivas.

Finalmente, notei que você tem chamadas de exclusão em ambos PStart e pNextWord em seu destruidor. Em primeiro lugar, para eliminar matrizes, você precisa usar delete [] ptr; (ou seja, array excluir). Em segundo lugar, você não iria excluir tanto pNextWord porque os pontos pNextWord PStart e na matriz PStart. Em terceiro lugar, por fim, PStart pontos não mais para o início da memória, então você precisaria de um membro separado para armazenar o início original para a chamada delete []. Por último, essas matrizes são alocados na pilha e não a pilha (ou seja, usando char var[], não char* var = new char[]), e, portanto, não deve ser excluída. Portanto, você deve simplesmente usar um destruidor vazia.

Outra dica útil é para contar o número de chamadas new e delete; deve haver o mesmo número de cada um. Neste caso, você tem zero de chamadas new, e duas chamadas delete, indicando um problema sério. Se fosse o contrário, seria indicar um vazamento de memória.

Outras dicas

Uma violação de acesso (ou "falha de segmentação" em alguns sistemas operacionais) significa que você tentou ler ou escrever para uma posição na memória que você nunca alocado.

Considere o loop while em Next ():

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

Vamos dizer o string é "blah\0". Note que eu incluí o nulo de terminação. Agora, pergunte-se: como é que esse ciclo saber parar quando atingir o final da string

Mais importante: o que acontece com *pStart se o loop não para parar no final da string

No interior :: Em seguida, você precisa verificar o caráter delim, mas você também precisa verificar para o final do buffer, (que eu estou supondo que é indicado por um \ 0).

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

E eu acho que estes testes em :: Next

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

Deve ser esta vez.

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

Isto é, você deve verificar para um personagem Nul, não um ponteiro nulo. A sua não é claro se você pretende para esses testes para detectar um ponteiro não inicializado PStart, ou o fim do buffer.

Uma violação de acesso geralmente significa um ponteiro ruim.

Neste caso, a causa mais provável é ficar sem corda antes de encontrar o seu delimitador.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top