0

Using MPLABX 5.35 with XC16 v1.36 for a PIC24FJ128GB204. Limited c background, but learning.

I'm trying to write a function that takes a (pointer to a) string in a char array (of unknown size) and edits it in-place to right-justify it and pad it with characters until it has the requested length. As I would like to keep this function for other projects as well, the size of the source array will be unknown, but the user (me) should keep targetLength smaller than the size of the array.

So imagine the array:

char example[20] = "test";

I want to be able to pass it to the function with the desired length (say, 10 chars incl null termination) and the fill character '#' and it should edit the array in-place to "#####TEST"

The code I came up with (which only works for some occasions so isn't usable):

uint16_t fillStringRight(char * source, 
                    uint16_t targetLength, 
                    char filler)
{
    uint16_t sourceLength = strlen(source) + 1;

    if (sourceLength > targetLength){ 
        // source length is already longer than requested
        return 0;
    }

    uint16_t reqFiller = targetLength - sourceLength;

    strcpy(&source[reqFiller], source);
    memset(source, filler, reqFiller -1);
    return 1;
}

However, in this case:

char source[20] = "test";
fillStringRight(source, 6, ' ');

This does not work. If I understand correctly, the source and result overlap, so strcpy will overwrite the null-terminator and thus keep on writing further and further into the memory until the watchdog restarts the PIC.

First question: is my understanding correct?

I suppose I should make a copy of the source string instead of editing it in-place. However, I don't know the length of the string so I can't size the char array that will be used for the copy (unless I make it ridiculously large). I read about malloc to create a variable in the heap with a size that is determined at runtime, but also read against using malloc on a microcontroller. I do not understand why yet, so prefer to steer away from it until I understand better.

Second question: what would be a correct way to do this? I could write a function that works backwards (start with a null terminator at the last position, then work backwards), but that seems like a lot of overhead. Is there not a better way?

1 Answers1

1

From man memmove(3):

The memory areas may overlap: copying takes place as though the bytes in src are first copied into a temporary array that does not overlap src or dest, and the bytes are then copied from the temporary array to dest.

In other words, there's a function for what you want :)
Try memmove(&source[reqFiller], source, sourceLength); A pseudo code equivalent:

char *tmp = duplicate_string(source);
copy tmp to &source[reqFiller]; 

Unrelated, but FYI:
1) strlen() returns size_t, not uint16_t
2) &a[b] == &*(a +b) == (a + b) so &source[reqFiller] == source + reqFiller

Hello World
  • 305
  • 1
  • 9
  • Thanks, that does indeed solve the issue. I didn't think of the term "overlapping" while trying to get to grips on the problem. That would have helped finding the solution myself. – Dieter Vansteenwegen ON4DD Apr 09 '20 at 07:42
  • @Hello_World Just out of curiosity, would there have been an "easy" way to make a copy without reserving a ridiculous amount of memory (because I don't know the size of the string until runtime)? Would using the heap be the "best" option? – Dieter Vansteenwegen ON4DD Apr 09 '20 at 07:44
  • That entirely depends on the size of the string. Default stack size, though completely platform/settings dependent, is usually a couple MEGAbytes. As a rule of thumb, you only get the signature stack overflow error with things like unlimited recursion; not big arrays. You should just be able to declare an array of the appropriate size with C99 style VLAs assuming your compiler supports them. If not, yes, use malloc(). The performance hit shouldn't be too large. As to making the copy, making the array and calling `memcpy()` is probably about as fast as you're gonna get. – Hello World Apr 09 '20 at 18:40
  • BTW, note that `memmove()` doesn't necessarily create a temp copy. It just acts "as though" it does. I believe (without looking it up) that how it is implemented isn't standardized; just what it does. – Hello World Apr 09 '20 at 18:42
  • I'm working with a pic that has 128KB of flash, so I am limited there. However, I will look up what VLA's are. Thanks. – Dieter Vansteenwegen ON4DD Apr 09 '20 at 18:48
  • Right. Sorry. I looked at your code and completely missed your machine info. A micro controller is another story, and I'm probably not the right person to ask. However, my 2 cents is that if you want to optimize totally for memory, not speed, I would do as you suggested: start at the end of the string, and copy it in reverse order so as to eliminate the temp entirely, as your question asks. Alternatively, inline assembly to do the job of `memmove()` if you don't trust it. – Hello World Apr 09 '20 at 19:44