【问题标题】:Unable to Allocate Memory to a Unique Addresses in C++无法将内存分配给 C++ 中的唯一地址
【发布时间】:2011-12-31 20:37:57
【问题描述】:

对于课程,我正在用 C++ 创建一个 shell。自从我使用 C++ 以来已经有一段时间了,所以我遇到了一些麻烦。该项目的要求之一是我必须使用 read() 系统调用。

我需要保留存储最近 20 条命令的命令历史记录(类似于 bash,如果您按向上箭头)。我觉得最好的方法是使用指向先前语句的指针数组。我遇到了一个问题,无论我做什么,包含用户输入的字符串总是存储在内存中的相同位置。澄清一下,这意味着,如果用户输入 5 条语句,然后查看他/她的历史记录,他们将看到最近的语句 5 次。我的代码看起来有点像这样(我不得不删掉一些东西,因为中间有很多错误处理):

char *history[20];
int historyCounter = 0;

while(true){
  char currLine[65];
  int charsRead = read(0,currLine,65);

  char tmp[charsRead];
  strcpy(tmp,currLine); //This is my attempt to ensure the char[] is stored int a 
                        //unique location every time, but this attempt failed.

  history[historycounter] = tmp;
  historycounter++;
}

请注意,在我的消息来源中,我确实处理了 historycounter > 19 的情况。只是不在这个 sn-p 中。

如果需要进一步说明,我很乐意提供。这是我第一次在堆栈溢出上发帖,所以如果我犯了任何新手错误,我提前道歉。如果解决方案非常明显,我也深表歉意。我已经研究了一段时间,完全有可能我只是想不直。

【问题讨论】:

  • 不要在此处使用 strcpy - read 不会以空值终止其输出,因此 charsRead 不包含用于终止空字符的空格,也不存在任何终止空字符。您应该使用 memcpy(并明确跟踪长度)或添加终止字符(并确保在复制时为其分配空间)。

标签: c++ arrays memory pointers


【解决方案1】:

在这种情况下,tmp 最有可能位于堆栈上的同一位置。它在每次迭代时分配,并在迭代结束时释放。

线

history[historycounter] = tmp;

使用它时会导致未定义的行为,因为您将使用其范围之外的局部变量的地址。

如果你想确保唯一的地址(并解决 UB 问题) - 使用 new 分配内存,不要使用 delete 直到你完成。确保跟踪所有分配的指针,并在完成后跟踪它们delete

【讨论】:

  • 哇...这解决了我的问题。我现在感觉自己像个白痴,但我绝对不怕再问另一个问题。非常感谢您快速而有帮助的回复!我真的很感激!
  • @Tom 任何时候,对于第一次来,你的问题都非常清楚地表达了,并且在主题上,你会惊讶于它是多么令人耳目一新。不要忘记接受答案。
  • 更好的是,不要使用new。使用std::string,或者std::vector<char>。更好的是,一个不会导致碎片的内存池。
  • @DeadMG 不知道否决答案的意义,只是因为您不喜欢 C++。 OP问了一个我回答的非常具体的问题。如果我回答错了 - 请分享反对意见。但是您基本上对我的回答投了反对票,因为您不喜欢 OP 编写程序的方式。为什么是我的错?从什么时候开始 new 和 delete 不好?没有人告诉我这件事。一点礼貌和常识会让你成为一个更好的人。
  • @littleadv:我已经有一段时间没有看到这个问题了,但让我告诉你,手动跟踪指针和deleteing 它们是不是 C++。事实上,在一个应该用 C++ 编写的程序中这样做是一种罪恶。 DXM 有正确的答案。
【解决方案2】:

你是用 C++ 还是 C 做作业?

littleadv 告诉您为什么您的程序运行不正确。您从未分配过字符串数组,而是只有一个字符串指针数组,但它们最终都指向 tmp 标识的同一堆栈位置。

但是,与 C 不同的是,C++ 并没有自己进行指针操作,而是为您提供了几个更高级别的选项。例如,您可以将数组声明为

std::vector< std::string >      listOfCommands;

这将为您处理所有内存管理。另外,不要使用直接的全局变量,而是考虑创建一个类来封装最后 N 个命令的存储和检索。为访问该列表提供最少的公共函数集(例如数组和 std::vectors 允许您修改任何元素,但在您的应用程序中,您只想写到最后,因此您应该只有一个编写器函数,仅写到最后)。您的 listOfCommands 将成为私有数据,您的程序的其余部分甚至不需要知道这些命令是如何存储在内存中的。

这就是使用 C++(以及任何其他 OO)语言进行编程的意义所在。代替全局变量,创建隐藏大部分复杂性的独立构建块(即类),然后创建依赖于更简单块的更大块。继续这样做,直到完成整个应用程序。

【讨论】:

  • 为什么只有这个人提倡正确的解决方案? WinRAR。
  • @DeadMG 因为有时你应该做你被告知的事情,即使 SO 上的一些聪明的驴子认为它“坏”。这个答案没有回答问题。它提供了另一种做事的方式(您认为“正确”,尽管我不知道因为何时使用动态分配变得不正确),但这不是 OP 所要求的。
【解决方案3】:

只需使用二维数组并跳过所有关于尝试确保唯一地址和 tmp 缓冲区的内容。

const size_t MAX_LINE_LENGTH = 65;
typedef char HISTORY_LINE[MAX_LINE_LENGTH+1]; //+1 for null terminator
const size_t MAX_HISTORY_LENGTH = 20;
HISTORY_LINE history[MAX_HISTORY_LENGTH];
int historyCounter = 0;

while(true){
  char* currLine = history[historycounter];

  currLine[0] = '\0';
  int charsRead = read(0,currLine,MAX_LINE_LENGTH);
  if(charsRead > 0)
      currLine[charsRead] = '\0'; // ensure string is null terminated

  historycounter++;
}

我跳过了所有与您相同的错误检查,但添加了一行以确保字符串始终以空值结尾。

【讨论】:

  • 如果每个字符串的数量和大小有一个固定的限制,这比使用动态内存管理要好得多,因为它不会导致内存碎片(对于长时间运行的进程很重要,例如一个外壳)。
  • @DeadMG:为什么?常量的名字很好,使用常量比散布在原始代码中的幻数要好得多。如果您反对行长限制,请与 OP 讨论。
  • OTOH,@selbie 没有理由将 currLine 数组与历史记录分开。只需执行char* currLine = history[historyCounter];。并让historyCounter 环绕并重用条目,而不是跑出数组的末尾。
  • 当然...但我假设他想在将刚刚读入的字符串添加到历史记录之前对其进行操作。那是代码中没有看到的部分……让我看看我能做什么……
  • @DeadMG - 我只是提供一个简单的解决方案,其直接目的是不分配内存 - 因为这本质上是 OP 最初所拥有的。换句话说,我只是在“清理他的代码”,而不是试图提出理想的解决方案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-27
  • 1970-01-01
相关资源
最近更新 更多