skipping spaces in C using pointers

Viewed 794

I've been asked to do this:

Make a skip_spaces() function accepting a string s, that returns a reference to the first element in the array that is not a space character (if the string is only composed of spaces, the pointer will address the null terminator \0). Then make a main program principal calling this function with a string read on stdin. From the given result, the program will then display the string from the first non-space char."

I've only started using pointers, and I'm clearly not a C expert, so I'm pretty lost here. Here is what I got so far:

In skip_spaces.c I have:

char *skip_spaces(char *s[]) {
  char *ref = '\0';
  int i = 0;
  while (*s[i] != '\0') {
    if (*s[i] == ' '):
      i++;
    else {
      *ref = *s[i];
    }
  }
}

In skip_spaces.h I have:

char *skip_spaces(char *);

And my main program:

#include "skip_spaces.h"
#include <stdio.h>

int main(void) {
    int input;
    char *str[30];
    char *spaceless;
    printf("input string : ");
    input = scanf("%s", str);
    if (input == 1) {
        int i = 0;
        spaceless = skip_spaces(str);
        printf("modified string : %s.", spaceless);
    return -1;
}

Now, I'm not sure yet if the program even does what I want it to do.

My issue here is that I can't even test it out at this point: I've tried a loooot of stuff, I can never compile properly, whenever I fix an issue somewhere, I get another issue else where. Pretty much all errors come from my main program.

I have two very persistent errors:

  • error: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘char **’

This error points at my input = scanf line, more precisely to my str var

  • skip_spaces.h:1:8: note: expected ‘char *’ but argument is of type ‘char **’

I've tried fidgetting, placing * here and & there, but either I have these 2 errors, either I have a lot more others...

I've even found several working codes for this exact function on the Internet (most were from SO actually), but whenever I try to implement their working solution in my code, I get these errors again. 100% certain my problem comes from my comprehension of pointers. I hope someone can shed some light.

3 Answers

Working code

This simple function removes leading spaces from a string:

/* remove leading spaces from string */
void rmspaces(char **str)
{
    while (**str == ' ')
        (*str)++;
}

It can be called like this:

char *str = "  hello";
rmspaces(&str);

Explanation

Your approach it to create a whole new string without the leading spaces, but it's way simpler to pass a pointer to the pointer that points to the first character of the string. Then you can just move the pointer *str to the next character using (*str)++; while the character is a space.

This has the advantage that no new string has to be allocated every time the function is called, because the old string can be reused.

There is also no need to check if the current character is the terminating null character '\0', because that is automatically assured by the condition in the while-loop.

/* sufficient */
while (**str == ' ')

/* unnecessary */
while (**str != '\0' && **str == ' ')

User input

If you scan for user input using scanf, leading whitespaces will automatically be removed as mentioned in Vlad from Moscow's answer.

char str[20];
/*
 * prevent buffer overflow and
 * take null character into account 
 */
scanf("%19s"); 

If you don't want scanf to automatically trim leading whitespaces, you could use fgets. That may be helpful if you only want to trim simple spaces ' ' using the function void rmspaces(char **str).

char str[20];
/* prevent buffer overflow */
fgets(str, 20, stdin);

Whitespace vs. space

There is a difference between whitespaces and spaces. A whitespace could be something like a tab '\t' or a newline '\n', while a space can only be ' '.

This is how you check if a character is a whitespace:

/* `c` is an `unsigned char` */
if (isspace(c))

This is how you check if a character is a space:

/* `c` is an `unsigned char` */
if (c == ' ')

This declaration

char *str[30];

does not make a sense. It declares an array of pointers while you need to declare a character array that will contain a string.

char str[30];

The conversion specifier used in this call

input = scanf("%s", str);

skips leading white spaces, so it also does not make a sense because the entered string will not contain leading white spaces. Instead, use the standard function fgets.

The parameter of the function skip_spaces declared like

char* s[]

as it was mentioned above is incorrect. You need to pass to the function a string. So the parameter should be declared like

const char *s

Pay attention to the qualifier const. It says the user of the function that the string itself will not be changed within the function.

Within the function skip_spaces this declaration

char* ref = '\0';

declares a null pointer. Thus dereferencing it

  *ref = *s[i];

invokes undefined behavior.

Also, the set of white space does not consist only the one character ' '. For example, the user can type the tab character '\t'.

And the output message

printf("modified string : %s.",spaceless);

is confusing. The source string is not modified. The function just returns a pointer to the first non-white space character. The string itself stays unchanged.

The function can be declared and defined as it is shown in the demonstrative program below.

#include <stdio.h>
#include <ctype.h>
#include <string.h>

char * skip_spaces( const char *s  )
{
    while ( *s && isspace( ( unsigned char )*s ) ) ++s;
    
    return ( char * )s;
}

int main(void) 
{
    enum { N = 30 };
    char str[N];
    
    printf( "Input a string (no more than %d characters): ", N );
    
    if ( fgets( str, N, stdin ) )
    {
        str[ strcspn( str, "\n" ) ] = '\0';
        printf( "The left trimmed string is \"%s\"", skip_spaces( str ) );
    }

    return 0;
}

If to enter the string " Hello World!" then the program output will look like

Input a string (no more than 30 characters):           Hello World!
The left trimmed string is "Hello World!"

C already provides a function that can do that for you. The strspn(const char *s, const char *accept) function will return the initial number of characters in s made up of characters in the accept string. See man 3 strspn

If you use " \t\n" (for space, tab, newline) for accept then the function returns the number of leading whitespace characters in the string s. If s is all whitespace, it returns the number of characters in s.

All you need to do is return s + strspn (s, " \t\n") and you have your answer, e.g.

const char *skip_spaces (const char *s)
{
    return s + strspn (s, " \t\n");     /* return pointer to 1st non-space or '\0' */
}

A full example would be:

#include <stdio.h>
#include <string.h>

const char *skip_spaces (const char *s)
{
    return s + strspn (s, " \t\n");     /* return pointer to 1st non-space or '\0' */
}

int main (void) {
    
    const char *str[] = { "     w/leading space",
                          "w/o leading space",
                          "         \t  " };
    size_t n = sizeof str/sizeof *str;
    
    for (size_t i = 0; i < n; i++) {
        if (!*skip_spaces (str[i]))
            printf ("skip_spaces (str[%zu]): '%s' (all spaces)\n", 
                    i, skip_spaces (str[i]));
        else
            printf ("skip_spaces (str[%zu]): '%s'\n", i, skip_spaces (str[i]));
    }
}

Example Use/Output

$ ./bin/skip_spaces
skip_spaces (str[0]): 'w/leading space'
skip_spaces (str[1]): 'w/o leading space'
skip_spaces (str[2]): '' (all spaces)

There is always more than one way to skin-the-cat in C :)

Also when visiting the man page, note the companion function strcspn (const char *s, const char *reject) does just the opposite, returning the initial number of characters in s NOT containing any of the characters in reject. (quite useful for trimming the '\n' at the end of buffers filled by fgets() or POSIX getline()).

Related