3

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.

First off, calling strcpy itself doesn't cause any sort of crash/segmentation fault itself. 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; memory is pretty limited, so I can see why things like malloc may fail, but not free.

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.

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.

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--;
}
Tagc
  • 8,736
  • 7
  • 61
  • 114
  • We don't complain about `strcpy`. We complain about *people using* `strcpy`. – Kerrek SB Aug 31 '12 at 14:43
  • Why do you never check the return from malloc()? It might be NULL. Why do you use trimMessage after it was freed? That's undefined behavior. Why do you cast the result of malloc()? That's a no-no. Why do you multiply with sizeof(char)? That's guaranteed to be 1 by the C Standard. Why is this code so convoluted? – Jens Aug 31 '12 at 14:48
  • @Jens I have checked the return from malloc, I've since removed the code for that, though. I don't use trimMessage after it's freed. I set it to null. "if(trimTimes == 1) free(trimMessage);" is to illustrate the fact that calling free() right after strcpy() will cause the crash, regardless of what comes afterwards. I multiply by sizeof(char) just because I've seen it in other code and thought it can't hurt. – Tagc Aug 31 '12 at 14:52
  • When `trimTimes` is 1, you're using `trimMessage` after it's been `free`'d, that's undefined behaviour. Also in that case, you `free` it twice, more UB. – Daniel Fischer Aug 31 '12 at 14:53
  • related Indirectly: after each *alloc() call, you must check if the return-value has non-zero value. – Jack Aug 31 '12 at 14:57

3 Answers3

12

free can and will crash if the heap is corrupt or if you pass it an invalid pointer.

Looking at that, I think your first malloc is a couple of bytes short. 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. 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).

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.

Tom Tanner
  • 9,244
  • 3
  • 33
  • 61
  • 1
    +1. It helps to show how you calculate the length, even if only in a comment: you need 1 byte for the nul terminator, one for the `\f`, and `msgSize - (trimIndex+1)` for the body. – Useless Aug 31 '12 at 14:54
  • @Tom Ty, I will try this out. My logic for (msgSize - trimIndex - 1) was that msgSize was the length of the original string, sans the null byte. trimIndex gives the index of the last character of the part I want to delete from that, so I want to delete (trimIndex+1) characters from the string. I didn't think I'd need to reserve space for the null byte of trimMessage because I'd just be using it to ferry characters between the old currMessage and the new one, but that's probably not good practice and may be causing the error. – Tagc Aug 31 '12 at 15:05
2

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. 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.

In this case, the problem was not so much strcpy() as miscalculation.

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;
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • Thanks for the code. Also, good advice about being consistent, I usually try to be, but at the time I was trying about as many different things as possible to fix my code, so it wasn't in a very clean state. – Tagc Aug 31 '12 at 15:36
0

Add this code just before the malloc() call:

// 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/).

And as a final edit, here's an alternative, simplified trimMessage() that I believe to be equivalent:

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

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

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

  messageCount--;
}
Michael Burr
  • 333,147
  • 50
  • 533
  • 760