【问题标题】:Access Violation With Pointers? - C++指针访问冲突? - C++
【发布时间】:2010-02-08 03:17:18
【问题描述】:

我为最近的一个学校项目编写了一个使用指针的简单字符串标记程序。但是,我的StringTokenizer::Next() 方法遇到了问题,该方法在调用时应该返回指向char 数组中下一个单词的第一个字母的指针。我没有收到编译时错误,但我收到了运行时错误,其中指出:

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

程序当前对 char 数组进行标记,但随后停止并弹出此错误。我感觉这与我在 Next() 方法中所做的 NULL 检查有关。

那么我该如何解决这个问题?

另外,如果您发现我可以做得更有效或通过更好的练习,请告诉我。

谢谢!!


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


编辑:

好的,只要分隔符是空格,程序现在就可以正常运行了。但是,如果我将 `/' 作为分隔符传递给它,它会再次出现访问冲突错误。有什么想法吗?

使用空格的函数:

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

【问题讨论】:

    标签: c++ pointers tokenize runtime-error


    【解决方案1】:

    访问冲突(或某些操作系统上的“分段错误”)意味着您尝试读取或写入内存中从未分配过的位置。

    考虑 Next() 中的 while 循环:

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

    假设字符串是"blah\0"。请注意,我已经包含了终止空值。现在,问问自己:当循环到达字符串末尾时,它是如何知道停止的?

    更重要的是:如果循环未能在字符串末尾停止,*pStart 会发生什么?

    【讨论】:

      【解决方案2】:

      这个答案是根据编辑过的问题和其他答案中的各种 cmets/观察结果提供的......

      首先,调用 Next() 时 pStart 的可能状态是什么?

      1. pStart 为 NULL(默认构造函数或设置为 NULL)
      2. *pStart 为 '\0'(字符串末尾的空字符串)
      3. *pStart 是分隔符(相邻分隔符处的空字符串)
      4. *pStart 是其他任何东西(非空字符串标记)

      此时我们只需要担心第一个选项。因此,我会在这里使用原始的“if”检查:

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

      为什么我们还不需要担心案例 2 或 3?您可能希望将相邻的分隔符视为在它们之间有一个空字符串标记,包括在字符串的开头和结尾。 (如果没有,请根据口味调整。)如果您还添加了 '\0' 检查(无论如何都需要),while 循环将为我们处理这个问题:

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

      在while循环之后是你需要小心的地方。现在有哪些可能的状态?

      1. *pStart 为 '\0'(标记在字符串末尾结束)
      2. *pStart 是分隔符(标记在下一个分隔符处结束)

      请注意,此处 pStart 本身不能为 NULL。

      您需要为这两种条件返回 pNextWord(当前令牌),这样您就不会丢弃最后一个令牌(即,当 *pStart 为 '\0' 时)。代码正确处理案例 2,但不正确处理案例 1(原始代码危险地递增 pStart 超过 '\0',新代码返回 NULL)。此外,正确重置案例 1 的 pStart 也很重要,这样下一次调用 Next() 将返回 NULL。我会把确切的代码作为练习留给读者,因为它毕竟是家庭作业;)

      概述整个函数中数据的可能状态是一个很好的练习,以确定每个状态的正确操作,类似于正式定义递归函数的基本案例与递归案例。

      最后,我注意到您在析构函数中对 pStart 和 pNextWord 都进行了删除调用。首先,要删除数组,需要使用delete [] ptr;(即数组删除)。其次,您不会同时删除 pStart 和 pNextWord,因为 pNextWord 指向 pStart 数组。第三,到最后,pStart 不再指向内存的开始,因此您需要一个单独的成员来存储 delete [] 调用的原始开始。最后,这些数组分配在堆栈上而不是堆上(即使用char var[],而不是char* var = new char[]),因此不应删除它们。因此,您应该简单地使用一个空的析构函数。

      另一个有用的技巧是计算newdelete 的调用次数;每个应该有相同的数量。在这种情况下,您有零个new 调用和两个delete 调用,表明存在严重问题。如果相反,则表明内存泄漏。

      【讨论】:

      • 谢谢,这真的很有帮助!
      【解决方案3】:

      在 ::Next 内部,您需要检查分隔符,但您还需要检查缓冲区的结尾,(我猜它由 \0 表示)。

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

      我认为这些测试在 ::Next

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

      应该是这个。

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

      也就是说,您应该检查 Nul 字符,而不是空指针。不清楚您是否打算让这些测试检测未初始化的 pStart 指针或缓冲区的结尾。

      【讨论】:

      • 请注意,他在无参数构造函数中将指针设置为 NULL。
      • @Anon:是的,但是他使用了另一个构造函数,所以 NULL 测试可能是为了防弹,也可能不是。
      • 我应该摆脱我的 NULL 检查吗?真的没有意义吗?
      • 好的,现在我的程序可以工作了,但是如果我给它传递一个不同的分隔符,它会再次以访问冲突终止。很奇怪。
      • @Alex:NULL 检查并非毫无意义,但在这种情况下,检查字符串的结尾更为重要,不清楚您的意思是否将 null 检查作为字符串检查结束。
      【解决方案4】:

      访问冲突通常意味着错误的指针。

      在这种情况下,最可能的原因是在找到分隔符之前字符串用完了。

      【讨论】:

      • 在我的程序中添加错误检查的最佳方法是什么?
      • 当您遍历字符串以及检查分隔符时,请检查终止 null ,这意味着字符串的结尾。如果你找到了,就停在那里并返回字符串。
      猜你喜欢
      • 2022-01-10
      • 1970-01-01
      • 1970-01-01
      • 2017-08-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-17
      • 1970-01-01
      相关资源
      最近更新 更多