chrisgjh chrisgjh - 3 months ago 16
C Question

how to use pointer to pointer to slice string

I read about pointer to pointers on tutorialsPoint.

I had a little test myself. I want to slice a string by space so that every words (include the punctuation) are treated as a token and the tokens are returned line by line.

Here is the code:

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

char** split(const char* s)
{
int i = 0, j = 0;
char** word = malloc(strlen(s)+1);
* word = malloc(strlen(s)+1);

while (*s != '\0') {
if (*s == ' ') {
i++;
} else {
word[i][j] = *s;
j++;
}
i++;
s++;
}

return word;
//free(word); //with or without this i get the same outcome.
}

int main(void)
{
char** words = split("He said 'hello' to me!");
int i = 0;
while (words[i] != NULL) {
puts(words[i]);
free(words[i]);
i += 1;
}
free(words);
}


It compiles but when I run on terminal I get segmentation fault. I added a
printf
in my if statement, it prints every letter.

I also used
valgrind
, but I cannot understand what it is saying.

expected output:

He
said
'hello'
to
me!

Answer

First, I will comment on your attempt:

Here:

return word;
free(word);

free won't be executed! You see anything that lies after the return statement, doesn't execute!

Moreover, the space allocated dynamically with malloc() is wrong, check about how we do that in my 2D dynamic array, or/and read the example below.

You see, without having allocated the sufficient, you were accessing out of bound memory, which would cause Undefined Behavior, but you are lucky to get a segmentation fault, which alerted you! :)

I won't debug your code, since it's a wonderful chance to practice, but if you want ask more.

Here is how I would do it, it's a simpler approach, but if you get that, then you will be able to work on your own attempt! :)

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

// We return the pointer
char **get(int N, int M) // Allocate the array */
{
    // TODO: Check if allocation succeeded. (check for NULL pointer)
    int i;
    char** table;
    table = malloc(N*sizeof(char *));
    for(i = 0 ; i < N ; i++)
        table[i] = malloc( M*sizeof(char) );
    return table;
}

void free2Darray(char** p, int N) {
    int i;
    for(i = 0 ; i < N ; i++)
        free(p[i]);
    free(p);
}

void zeroFill(char** p, int N, int M) {
    int i, j;
    for(i = 0 ; i < N ; i++)
        for(j = 0 ; j < M ; j++)
            p[i][j] = 0;
}

void print(char** p, int N, int M) {
    int i;
    for(i = 0 ; i < N ; i++)
        if(strlen(p[i]) != 0)
            printf("array[%d] = %s\n", i, p[i]);
}

void split(const char* s, char** words) {
    int i = 0, word_idx = 0, char_idx = 0;
    while(s[i] != '\0') {
        if(s[i] != ' ') {
            words[word_idx][char_idx++] = s[i];
        } else {
            word_idx++;
            char_idx = 0;
        }
        ++i;
    }
}

int main(void)
{
    char** words = get(10, 15); // 10 words, 14 chars max (+1 for n$
    zeroFill(words, 10, 15);

    split("He said 'hello' to me!", words);
    print(words, 10, 15);

    free2Darray(words, 10);
    return 0;

}

Output:

C02QT2UBFVH6-lm:~ gsamaras$ nano main.c
C02QT2UBFVH6-lm:~ gsamaras$ gcc -Wall main.c 
C02QT2UBFVH6-lm:~ gsamaras$ ./a.out 
array[0] = He
array[1] = said
array[2] = 'hello'
array[3] = to
array[4] = me!

The explanation:

  1. I assume a sentence will have at most 10 words, and every word will be at most 14 characters long (+1 for the NULL-terminator).
  2. Allocate dynamically a 2D array, of 10 rows and 15 columns.
  3. Initialize all of its elements to zero. I did so just to make sure the words are NULL terminated. I could of course skip that step and insert the NULL terminator manually at the exact position in split().
  4. Split the string in tokens.
  5. Print the words.
  6. Free the dynamically allocated memory of the 2D array.

Now, let me explain a bit more the split() function:

Actually your attempt was pretty good, so you probably know already what I am doing:

  1. I loop over the string, until I meet the NULL terminator.
  2. As long as I don't meet the whitespace, I insert the current character in the current word. word_idx helps me remember which word I am filling up now, and which character of that word with char_idx.
  3. When I meet the whitespace (the else case), I have to advance to the next word (no need to NULL-terminate the string I filled up before, since I have zero initialized my 2D array), by incrementing word_idx by 1. I also set char_idx to 0, so that I can start filling up the new word from position 0.