Domanda

Ho scritto un semplice programma di creazione di token di stringa utilizzando puntatori per un progetto scolastico recente. Tuttavia, Ho problemi con il mio metodo StringTokenizer::Next(), che, quando viene chiamato, deve restituire un puntatore alla prima lettera della parola successiva nella matrice char. Ottengo errori in fase di compilazione, ma ottengo un errore di runtime in cui si afferma:

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

Il programma attualmente tokenizza char, ma poi si ferma e questo errore si apre. Ho la sensazione che ha a che fare con il controllo NULL che sto facendo nel mio metodo Next().

Così come posso risolvere il problema?

Inoltre, se si nota qualcosa che potessi fare in modo più efficiente e con una migliore pratica, per favore fatemelo sapere.

Grazie !!


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;
}


Modifica

Va bene, ho il programma di lavoro bene ora, fino a quando il delimitatore è uno spazio. Ma se mi passa un `/' come delim, si tratta con l'errore di violazione di accesso di nuovo. Tutte le idee?

La funzione che lavora con spazi:

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;
}
È stato utile?

Soluzione

Questa risposta è fornita in base alla domanda modificato e vari commenti / osservazioni in altre risposte ...

In primo luogo, quali sono i possibili stati di PStart quando Next () si chiama?

  1. PStart è NULL (costruttore di default o comunque impostato su NULL)
  2. * PStart è '\ 0' (stringa vuota alla fine della stringa)
  3. * PStart è delim (stringa vuota ad un delimitatore adiacente)
  4. * PStart qualcos'altro (non vuota-stringa di token)

A questo punto abbiamo solo bisogno di preoccuparsi per la prima opzione. Pertanto, vorrei utilizzare l'originale "se" controllare qui:

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

Perché non abbiamo bisogno di preoccuparsi per i casi 2 o 3 ancora? Probabilmente si desidera trattare delimitatori adiacenti come avere un token vuoto stringa tra di loro, tra cui all'inizio e alla fine della stringa. (In caso contrario, regolare a piacere.) Il ciclo while gestirà che per noi, a condizione di aggiungere anche il '0 \' di controllo (necessario a prescindere):

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

Dopo il ciclo while è dove dovete stare attenti. Quali sono i possibili stati ora?

  1. * PStart è '\ 0' (Token termina alla fine della stringa)
  2. * PStart è delim (pedina termina al successivo delimitatore)

Si noti che PStart per sé non può essere NULL qui.

È necessario tornare pNextWord (token di corrente) per sia di queste condizioni in modo da non far cadere l'ultimo gettone (vale a dire, quando * PStart è '\ 0'). gestisce il codice di caso 2 in modo corretto, ma non il caso 1 (codice originale pericolosamente incrementato PStart passato '\ 0', il nuovo codice restituito NULL). Inoltre, è importante ripristinare PStart per caso 1 correttamente, in modo che la chiamata successiva a Next () restituisce NULL. Lascio il codice esatto come un esercizio per il lettore, dal momento che è a casa dopo tutto;)

E 'un buon esercizio per delineare i possibili stati di dati durante una funzione al fine di determinare l'azione corretta per ogni stato, simile a definire formalmente casi di base contro i casi ricorsive per funzioni ricorsive.

Infine, ho notato che hai eliminare le chiamate su entrambi i PStart e pNextWord nel distruttore. In primo luogo, per eliminare gli array, è necessario utilizzare delete [] ptr; (vale a dire, un array delete). In secondo luogo, non si dovrebbe eliminare sia PStart e pNextWord perché pNextWord punti nella matrice PStart. In terzo luogo, alla fine, PStart non più punti per l'inizio della memoria, in modo che avrebbe bisogno di un membro separato per memorizzare l'inizio originale per la chiamata delete []. Infine, queste matrici sono allocati sullo stack e non il mucchio (cioè, utilizzando char var[], non char* var = new char[]), e quindi non devono essere eliminati. Pertanto, si dovrebbe semplicemente utilizzare un distruttore vuoto.

Un altro suggerimento utile è di contare il numero di chiamate new e delete; si dovrebbe avere lo stesso numero di ciascuno. In questo caso, una chiamata è pari a zero new, e due chiamate delete, che indica un problema serio. Se fosse il contrario, sarebbe indicare una perdita di memoria.

Altri suggerimenti

Una violazione di accesso (o "segmentation fault" su alcuni sistemi operativi) significa che hai tentato di leggere o scrivere in una posizione in memoria che non avete mai assegnato.

Si consideri il ciclo while in Next ():

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

Diciamo che la stringa è "blah\0". Si noti che ho incluso il nulla di terminazione. Ora, chiedetevi: come fa quel loop sa di fermarsi quando raggiunge la fine della stringa

Ancora più importante: cosa succede con *pStart se il ciclo non per fermarsi alla fine della stringa

All'interno :: Next è necessario controllare per il personaggio delim, ma è anche necessario per verificare la fine del buffer, (che sto cercando di indovinare è indicato da un \ 0).

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

E penso che questi test in :: Next

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

Dovrebbe essere questo, invece.

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

Cioè, si dovrebbe essere in controllo di un personaggio Nul, non un puntatore nullo. La sua non è chiaro se si intende per questi test per rilevare un puntatore non inizializzato PStart, o la fine del buffer.

Una violazione di accesso di solito significa un puntatore errato.

In questo caso, la causa più probabile è a corto di corda prima di trovare la vostra delimitatore.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top