Search code examples
cchartrimc-stringsfunction-definition

Trim Characters off a String on the Stack?


I need a C function that takes a string (allocated on the stack, not on the heap) that may have the same character on the front and back; I want to trim off those characters. I may not know how many header/footer characters there are, nor will I necessarily always know what the offending character will be.

In other words, if my original string is:

xxxThis is a string.xxxx

…then I want…

This is a string.

returned. Ideally, I’d love a solution that looked like this:

char str1[50] = “xxxThis is a string.xxxx”;
str1 = trimString( str1 );
printf(“Returned string is:: %s\n”, str1);       // prints “This is a string.”

Here’s my code:

char* trimString( char* str1, char x ){
        int i = 0;
        int j = (int)strlen( str1 ) - 1;
        printf("string is :: >>%s<<\n", str1);
        while( str1[i] == x ){
                i++;
        }
        while( str1[j] == x ){
                j--;
        }

        str1 = strncpy( str1, str1+i, (j-i) );

        return str1;
}


int main(){

        char str1[50] = "xxxThis is a string.xxxx";
        str1[25] = '\0';
        printf("%s\n", trimString( str1, 'x' ) );
        printf("END OF PROGRAM.\n");
        return 0;
}

Here’s the output:

This is a stringing.xxxx
END OF PROGRAM.

There are two obvious problems. First, I think I’m correctly shaving off the ‘x’ characters, but when I copy the trimmed string back into the “str1” variable, I overwrite only the first n characters of the old string with the new string. The remains of original string is still there.

More seriously, I am not close to my goal of calling this function like this:

str1 = trimString( str1, ‘x’ );

I could call my function with something like this:

char tmpStr[50] = trimString( str1, ‘x’ );
memcpy( str1, tmpStr );

But that’s a pain and now I have to potentially have to worry about the size of the temporary strings. Also, I think I’ll have to call trimString() many, many, many times, so it would be great if I can it with one line of code and not have to worry about managing temporary strings, etc.

Any suggestions or advice?


Solution

  • Here you are.

    #include <stdio.h>
    #include <string.h>
    
    char * trimString( char *s, char c )
    {
        size_t i = 0;
        
        while ( s[i] == c ) i++;
        
        size_t n = strlen( s + i );
        
        while ( n && s[n + i - 1] == c ) --n;
        
        s[n + i] = '\0';
        
        if ( i != 0 )
        {
            memmove( s, s + i, n + 1 );
        }
        
        return s;
    }
    
    int main(void) 
    {
        char s[50] = "xxxThis is a string.xxxx";
        
        puts( s );
        puts( trimString( s, 'x' ) );
        
        return 0;
    }
    

    The program output is

    xxxThis is a string.xxxx
    This is a string.
    

    As for your function implementation then it can invoke undefined behavior when for example the user passes an empty string. In this case the value of the variable j can be negative

        int j = (int)strlen( str1 ) - 1;
        
    

    and using this negative value the function will access memory beyond the character array.

        while( str1[j] == x ){
                j--;
        }
    

    And moreover there is no check that the current value of j becomes equal to or less than 0.

    Also you may not use the function strncpy

    str1 = strncpy( str1, str1+i, (j-i) );
    

    and in any case this call forgets to copy the terminating zero.

    Pay attention to that you need to move the string inside the character array only in the case when i is not equal to 0. Otherwise it is enough to set properly the terminating zero.

    To make the function more safer you can check within the function whether the passed character (the second parameter) is equal to the terminating zero.

    For example

    #include <stdio.h>
    #include <string.h>
    
    char * trimString( char *s, char c )
    {
        if ( c != '\0' )
        {
            size_t i = 0;
        
            while ( s[i] == c ) i++;
        
            size_t n = strlen( s + i );
        
            while ( n && s[n + i - 1] == c ) --n;
        
            s[n + i] = '\0';
        
            if ( i != 0 )
            {
                memmove( s, s + i, n + 1 );
            }
        }
        
        return s;
    }
    
    int main(void) 
    {
        char s[50] = "xxxThis is a string.xxxx";
        
        puts( s );
        puts( trimString( s, 'x' ) );
        
        return 0;
    }