2

我知道这里有很多人抱怨 strcpy,但我没有找到任何使用搜索来解决我遇到的问题的东西。

首先,调用 strcpy 本身不会导致任何类型的崩溃/分段错误。其次,代码包含在一个函数中,我第一次调用这个函数时它工作得很好。它只在第二次通过时崩溃。

我正在用 LPC1788 微控制器编程;内存非常有限,所以我明白为什么 malloc 之类的东西可能会失败,但不是免费的。

函数 trimMessage() 包含代码,该函数的目的是在大字符串数组变得太大时删除它的一部分。

void trimMessage()
{
  int trimIndex;
  // currMessage is a globally declared char array that has already been malloc'd
  // and written to.
  size_t msgSize = strlen(currMessage);

  // Iterate through the array and find the first newline character. Everything
  // from the start of the array to this character represents the oldest 'message'
  // in the array, to be got rid of.
  for(int i=0; i < msgSize; i++)
  {
    if(currMessage[i] == '\n')
    {
      trimIndex = i;
      break;
    }
  }
  // e.g.: "\fProgram started\r\nHow are you?\r".
  char *trimMessage = (char*)malloc((msgSize - trimIndex - 1) * sizeof(char));

  trimMessage[0] = '\f';

  // trimTimes = the number of times this function has been called and fully executed.
  // freeing memory just below is non-sensical, but it works without crashing.
  //if(trimTimes == 1) { printf("This was called!\n"); free(trimMessage); }
  strcpy(&trimMessage[1], &currMessage[trimIndex+1]);

  // The following line will cause the program to crash. 
  if(trimTimes == 1) free(trimMessage);
  printf("trimMessage: >%s<\n", trimMessage);

  // Frees up the memory allocated to currMessage from last iteration
  // before assigning new memory.
  free(currMessage);
  currMessage = malloc((msgSize - trimIndex + 1) * sizeof(char));

  for(int i=0; i < msgSize - trimIndex; i++)
  {
    currMessage[i] = trimMessage[i];
  }

  currMessage[msgSize - trimIndex] = '\0';
  free(trimMessage);
  trimMessage = NULL;

  messageCount--;
  trimTimes++;
}

感谢所有帮助过的人。该功能现在可以正常工作。对于那些问我为什么要打印出我刚刚释放的数组的人,那只是为了表明问题发生在 strcpy 之后,并排除了它之后出现的任何其他代码。

最终代码在这里,以防它对遇到类似问题的任何人有用:

void trimMessage()
{
  int trimIndex;
  size_t msgSize = strlen(currMessage);

  char *newline = strchr(currMessage, '\n'); 
  if (!newline) return;
  trimIndex = newline - currMessage;

  // e.g.: "\fProgram started\r\nHow are you?\r".
  char *trimMessage = malloc(msgSize - trimIndex + 1);

  trimMessage[0] = '\f';
  strcpy(&trimMessage[1], &currMessage[trimIndex+1]);

  trimMessage[msgSize - trimIndex] = '\0';

  // Frees up the memory allocated to currMessage from last iteration
  // before assigning new memory.
  free(currMessage);
  currMessage = malloc(msgSize - trimIndex + 1);

  for(int i=0; i < msgSize - trimIndex; i++)
  {
    currMessage[i] = trimMessage[i];
  }

  currMessage[msgSize - trimIndex] = '\0';
  free(trimMessage);

  messageCount--;
}
4

3 回答 3

11

如果堆损坏或传递给它一个无效的指针,free 就会崩溃。

看着那个,我认为你的第一个 malloc 是几个字节短。您需要为空终止符保留一个字节,并且要复制到偏移量 1,因此您需要为此保留另一个字节。所以将会发生的是,您的副本将在下一个堆块开始时覆盖信息(通常用于下一个堆块的长度以及是否使用它的指示,但这取决于您的 RTL )。

当您下一次执行 free 时,它​​可能会尝试合并任何空闲块。不幸的是,您已经损坏了下一个块的标题,此时它会变得有点疯狂。

于 2012-08-31T14:49:01.697 回答
2

比较这两行代码(当然,我已经重新调整了第二行):

char *trimMessage = (char*)malloc((msgSize - trimIndex - 1) * sizeof(char));
      currMessage =        malloc((msgSize - trimIndex + 1) * sizeof(char));

除了强制转换方面的不必要差异(一致性很重要;您使用的两种样式中的哪一种无关紧要,但不要在同一代码中同时使用这两种样式)之外,长度上有 2 个字节的差异。第二个比第一个更可能是正确的。

在第一种情况下,您分配的 2 个字节太少,并且副本覆盖了一些malloc()等人依赖的控制信息,因此free()崩溃是因为您损坏了它管理的内存。

在这种情况下,问题不在于strcpy()计算错误。

破坏内存的问题之一是受害者代码(发现问题的代码)通常与导致问题的代码相距甚远。


这个循环:

for(int i=0; i < msgSize; i++)
{
  if(currMessage[i] == '\n')
  {
    trimIndex = i;
    break;
  }
}

可以替换为:

char *newline = strchr(currMessage, '\n');
if (newline == 0)
    ...deal with no newline in the current messages...
trimIndex = newline - currMessage;
于 2012-08-31T14:58:45.143 回答
0

在调用之前添加此代码malloc()

// we need the destination buffer to be large enough for the '\f' character, plus
//      the remaining string, plus the null terminator
printf("Allocating: %d  Need: %d\n", (msgSize - trimIndex - 1), 1 + strlen(&currMessage[trimIndex+1]) + 1);

我认为它会告诉你问题。

一次又一次地证明,手动计算缓冲区大小可能容易出错。有时您必须这样做,但有时您可以让函数处理那些容易出错的方面:

// e.g.: "\fProgram started\r\nHow are you?\r".
char *trimMessage = strdup( &currMessage[trimIndex]);

if (trimMessage && (trimMessage[0] == '\n')) {
    trimMessage[0] = '\f';
}

如果您的运行时没有strdup(),它很容易实现(http://snipplr.com/view/16919/strdup/)。

作为最后的编辑,这里有一个替代方案,trimMessage()我认为它是等价的:

void trimMessage()
{
  char *newline = strchr(currMessage, '\n'); 
  if (!newline) return;

  memmove( currMessage, newline, strlen(newline) + 1);

  currMessage[0] = '\f';    // replace '\n'

  messageCount--;
}
于 2012-08-31T15:02:39.400 回答