0

最近の学校のプロジェクトのポインターを使用して、単純な文字列トークン化プログラムを作成しました。ただし、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;
}


編集:

さて、区切り文字がスペースである限り、プログラムは正常に機能しています。しかし、それをdelimとして `/'に渡すと、アクセス違反エラーが再び発生します。何か案は?

スペースで機能する関数:

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 に答える 4

6

アクセス違反(または一部のOSでは「セグメンテーション違反」)とは、割り当てたことがないメモリ内の位置に対して読み取りまたは書き込みを試みたことを意味します。

Next()のwhileループについて考えてみましょう。

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

文字列が。であるとしましょう"blah\0"。終了ヌルを含めたことに注意してください。さて、自問してみてください。そのループは、文字列の終わりに達したときに停止することをどのようにして知るのでしょうか。

さらに重要な*pStartのは、ループが文字列の最後で停止しなかった場合はどうなるでしょうか

于 2010-02-08T03:28:30.497 に答える
1

:: Nextの内部では、delim文字をチェックする必要がありますが、バッファーの終わりもチェックする必要があります(これは、\ 0で示されていると思います)。

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

そして、これらのテストは::Nextにあると思います

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

代わりにこれである必要があります。

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

つまり、nullポインタではなく、Nul文字をチェックする必要があります。これらのテストで初期化されていないpStartポインターを検出するのか、バッファーの終わりを検出するのかは明確ではありません。

于 2010-02-08T03:30:35.500 に答える
1

この回答は、編集された質問と他の回答のさまざまなコメント/観察に基づいて提供されています...

まず、Next()が呼び出されたときのpStartの可能な状態は何ですか?

  1. pStartはNULLです(デフォルトのコンストラクターまたはそれ以外の場合はNULLに設定されます)
  2. *pStartは'\0'(文字列の最後に空の文字列)です
  3. * pStartはdelimです(隣接する区切り文字の空の文字列)
  4. * pStartはそれ以外のものです(空でない文字列トークン)

この時点では、最初のオプションについてのみ心配する必要があります。したがって、ここでは元の「if」チェックを使用します。

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

ケース2または3についてまだ心配する必要がないのはなぜですか?隣接する区切り文字を、文字列の最初と最後を含め、それらの間に空の文字列トークンがあるものとして扱いたいと思うかもしれません。(そうでない場合は、好みに合わせて調整してください。)'\ 0'チェックも追加すると、whileループがそれを処理します(関係なく必要です)。

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

whileループの後は、注意が必要な場所です。現在可能な状態は何ですか?

  1. *pStartは'\0'です(トークンは文字列の最後で終了します)
  2. * pStartはdelimです(トークンは次の区切り文字で終了します)

ここでは、pStart自体をNULLにすることはできないことに注意してください。

最後のトークンをドロップしないように(つまり、*pStartが'\ 0'の場合)、これらの条件の両方に対してpNextWord(現在のトークン)を返す必要があります。コードはケース2を正しく処理しますが、ケース1は処理しません(元のコードは危険なほどpStartを「\ 0」を超えてインクリメントし、新しいコードはNULLを返しました)。さらに、Next()への次の呼び出しがNULLを返すように、ケース1のpStartを正しくリセットすることが重要です。結局のところ宿題なので、正確なコードは読者の練習問題として残しておきます;)

再帰関数の基本ケースと再帰ケースを正式に定義するのと同様に、各状態の正しいアクションを決定するために、関数全体でデータの可能な状態の概要を説明することをお勧めします。

最後に、デストラクタのpStartとpNextWordの両方でdelete呼び出しがあることに気付きました。まず、アレイを削除するには、を使用する必要がありますdelete [] ptr;(つまり、アレイの削除)。次に、pNextWordはpStart配列を指しているため、pStartとpNextWordの両方を削除することはありません。第3に、最後までに、pStartはメモリの開始を指さなくなるため、delete []呼び出しの元の開始を保存するために別のメンバーが必要になります。最後に、これらの配列はヒープではなくスタックに割り当てられるため(つまり、を使用してchar var[]ではなくchar* var = new char[])、削除しないでください。したがって、単に空のデストラクタを使用する必要があります。

もう1つの便利なヒントはnewdelete呼び出しの数を数えることです。それぞれの数は同じである必要があります。この場合、呼び出しはゼロnewで、呼び出しは2つでありdelete、重大な問題を示しています。逆の場合は、メモリリークを示しています。

于 2010-02-08T05:47:04.183 に答える
0

アクセス違反は通常、不正なポインタを意味します。

この場合、最も可能性の高い原因は、区切り文字を見つける前に文字列が不足していることです。

于 2010-02-08T03:21:13.083 に答える