简体   繁体   English

调用strcpy后尝试释放内存会导致程序崩溃

[英]Trying to free memory after calling strcpy causes the program to crash

I understand that a lot of people here complain about strcpy, but I haven't found anything using search that addresses the issue I have. 我知道这里有很多人抱怨strcpy,但是我没有使用搜索找到任何解决我所遇到问题的方法。

First off, calling strcpy itself doesn't cause any sort of crash/segmentation fault itself. 首先,调用strcpy本身不会引起任何崩溃/分段错误。 Secondly, the code is contained within a function, and the first time that I call this function it works perfectly. 其次,代码包含在一个函数中,并且我第一次调用该函数时,它可以完美地工作。 It only crashes on the second time through. 它仅在第二次崩溃。

I'm programming with the LPC1788 microcontroller; 我正在使用LPC1788微控制器进行编程。 memory is pretty limited, so I can see why things like malloc may fail, but not free. 内存是非常有限的,所以我可以看到为什么malloc之类的东西可能会失败,但不是免费的。

The function trimMessage() contains the code, and the purpose of the function is to remove a portion of a large string array if it becomes too large. 函数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++;
}

Thank you to everyone that helped. 谢谢所有帮助的人。 The function works properly now. 该功能现在可以正常工作。 To those asking why I was trying to print out an array I just freed, that was just there to show that the problem occurred after strcpy and rule out any other code that came after it. 对于那些问我为什么要打印刚刚释放的数组的人,那仅仅是为了表明问题是在strcpy之后发生的,并排除了之后的任何其他代码。

The final code is here, in case it proves useful to anyone who comes across a similar problem: 最终代码在这里,以防万一遇到类似问题的人有用:

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

free can and will crash if the heap is corrupt or if you pass it an invalid pointer. 如果堆损坏或您将其传递给无效指针,则free可能会崩溃。

Looking at that, I think your first malloc is a couple of bytes short. 综上所述,我认为您的第一个malloc短了几个字节。 You need to reserve a byte for the null terminator and also you're copying into offset 1, so you need to reserve another byte for that. 您需要为空终止符保留一个字节,并且还要将其复制到偏移量1中,因此您需要为此保留另一个字节。 So what is going to happen is that your copy will overwrite information at the start of the next block of heap (often used for length of next block of heap and an indication as to whether or not it is used, but this depends on your RTL). 因此,将要发生的事情是您的副本将在下一个堆块的开头覆盖信息(通常用于下一个堆块的长度以及是否使用该堆的指示,但这取决于您的RTL )。

When you next do a free, it may well try to coalesce any free blocks. 当您下次免费时,它可能会尝试合并所有免费块。 Unfortunately, you've corrupted the next blocks header, at which point it will go a bit insane. 不幸的是,您已经损坏了下一个blocks头,这时它会有点疯狂。

Compare these two lines of your code (I've respaced the second line, of course): 比较您的代码的这两行(当然,我在第二行换行了):

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

Quite apart from the unnecessary difference in casting (consistency is important; which of the two styles you use doesn't matter too much, but don't use both in the same code), you have a difference of 2 bytes in the length. 除了在转换方面产生不必要的差异(一致性很重要;您使用的两种样式中的哪一种无关紧要,但不要在同一代码中同时使用这两种样式)之外,您的长度相差2个字节。 The second is more likely to be correct than the first. 第二个比第一个更可能是正确的。

You allocated 2 bytes too few in the first case, and the copy overwrote some control information that malloc() et al depend on, so the free() crashed because you had corrupted the memory it manages. 在第一种情况下,您分配的2个字节太少,并且副本覆盖了malloc()等人依赖的某些控制信息,因此free()崩溃了,因为您破坏了它管理的内存。

In this case, the problem was not so much strcpy() as miscalculation. 在这种情况下,问题不仅仅在于strcpy() ,还在于计算错误。

One of the problems with corrupting memory is that the victim code (the code that finds the trouble) is often quite far removed from the code that caused the trouble. 内存损坏的问题之一是受害者代码(找到问题的代码)通常与引起问题的代码相去甚远。


This loop: 这个循环:

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

could be replaced with: 可以替换为:

char *newline = strchr(currMessage, '\n');
if (newline == 0)
    ...deal with no newline in the current messages...
trimIndex = newline - currMessage;

Add this code just before the malloc() call: 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);

And I think it will show you the problem. 而且我认为它将向您显示问题。

It's been demonstrated time and again that hand calculating buffer sizes can be error prone. 一次又一次地证明,手动计算缓冲区大小可能容易出错。 Sometimes you have to do it, but other times you can let a function handle those error prone aspects: 有时您必须这样做,但有时您可以让函数处理那些容易出错的方面:

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

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

If your runtime doesn't have strdup() , it's easy enough to implement ( http://snipplr.com/view/16919/strdup/ ). 如果您的运行时没有strdup() ,则很容易实现( http://snipplr.com/view/16919/strdup/ )。

And as a final edit, here's an alternative, simplified trimMessage() that I believe to be equivalent: 作为最后的编辑,这是我认为等效的替代方法,简化的trimMessage()

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

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

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

  messageCount--;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM