Home > Blockchain >  Problems when converting a string to a two-dimensional array
Problems when converting a string to a two-dimensional array

Time:12-31

I'm coming back to you about my function char **my_str_to_word_array(char *str). The purpose is to separate the string at each non-printable ASCII character and include the above in a new row of the double dimensional array.

Non-printable ASCII characters should be used as separators and should not be included in the line.

Example:

char *test = "My name is John Doe.\nI have 0 GPA.\nI will survive." ;
char **array = my_str_to_word_array(test) ;

array[0] = "My name is John Doe." (zero terminated string)
array[1] = "I have 0 GPA." (zero terminated string)
array[2] = "I will survive." (zero terminated string)
array[3] = NULL

I have 2 problems:

  1. If in my test main() I have a printf() below the call to my_str_to_word_array, the format passed to printf() will be included in the array. So I conclude that there is a memory read error.

  2. When I try to free() the array I get an error :

double free or corruption (out)
[1]    33429 IOT instruction (core dumped)  ./libmy
size_t get_words_number(char const *str)
{
    size_t count = 0;
    const char *i = str;
    while (*i != 0) {
        if (isprint(*i)) {
            count  ;
        }
        while (*i != 0 && isprint(*i)) {
            i  ;
        }
        i  ;
    }
    return count;
}

char **free_corrupted_array(char **array, size_t i)
{
    size_t j = 0;
    while (j < i) {
        free(array[j]);
        j  ;
    }
    free(array);
    return NULL;
}

char **fill_array(char **array, const char *str, size_t word_count)
{
    size_t word_size = 0, j = 0;
    const char *i = str;
    while (j < word_count) {
        while (*i != 0 && isprint(*i)) {
            word_size  ;
            i  ;
        }
        array[j] = strndup(i - word_size, word_size);
        if (!array[j]) {
            return free_corrupted_array(array, j);
        }
        word_size = 0;
        j  ;
        while (!isprint(*i)) {
            i  ;
        }
    }
    array[j] = NULL;
    return array;
}

char **my_str_to_word_array(char const *str)
{
    char **word_array = NULL;
    size_t word_count = 0;
    if (!str) {
        return NULL;
    }
    word_count = get_words_number(str);
    word_array = malloc(word_count * sizeof(char *));
    if (!word_array) {
        return NULL;
    }
    word_array = fill_array(word_array, str, word_count);
    return word_array;
}
void my_free_word_array(char **word_array)
{
    if (!word_array) {
        return;
    }
    while (*word_array != NULL) {
        free(*word_array);
        word_array  ;
    }
    free(word_array);
}
int main(int argc, char **argv)
{
    const char *test = "My name is John Doe.\nI have 0 GPA.\nI will survive.";
    char **word_array = my_str_to_word_array(test);
    while (*word_array != NULL) {
        printf("%s\n", *word_array);
        word_array  ;
    }
    printf("Test print original size %lu\n", strlen(test));
    my_free_word_array(word_array);
    return 0;
}

And the output :

My name is John Doe.
I have 0 GPA.
I will survive.
Test print original size %lu
Test print original size 50
double free or corruption (out)
[1]    33429 IOT instruction (core dumped)  ./libmy

Do you see the problem?

CodePudding user response:

Errors:

  1. get_words_number goes out of bounds (off by one) and may read arbitrary memory after your string (check with the example I included in main).
  2. You need an additional slot in your array to put there a terminating NULL.
  3. Stop thrashing your input pointer if you later need it (both in my_free_word_array and in the printing loop in main).

Suggestions:

  1. Next time make a Minimal, Reproducible Example by including all required headers;
  2. strndup is not standard (unless you have __STDC_ALLOC_LIB__ and define __STDC_WANT_LIB_EXT2__ to 1).
  3. You don't need the free_corrupted_array function at all.
#define _CRT_SECURE_NO_WARNINGS
#ifdef __STDC_ALLOC_LIB__
#define __STDC_WANT_LIB_EXT2__ 1
#else
#include <stdlib.h>
#include <string.h>
char *strndup(const char *str, size_t size)
{
    return strncpy(calloc(size   1, 1), str, size);
}
#endif

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

size_t get_words_number(char const *str)
{
    size_t count = 0;
    const char *i = str;
    while (*i != 0) {
        if (isprint(*i)) {
            count  ;
        }
        while (*i != 0 && isprint(*i)) {
            i  ;
        }
        if (*i != 0) { // <--- This was missing
            i  ;
        }
    }
    return count;
}

void my_free_word_array(char **word_array) // <--- Moved up
{
    if (!word_array) {
        return;
    }
    for (size_t i = 0; word_array[i] != NULL;   i) { // <--- Stop thrashing word_array
        free(word_array[i]);
    }
    free(word_array);
}

char **fill_array(char **array, const char *str, size_t word_count)
{
    size_t word_size = 0, j = 0;
    const char *i = str;
    while (j < word_count) {
        while (*i != 0 && isprint(*i)) {
            word_size  ;
            i  ;
        }
        array[j] = strndup(i - word_size, word_size); 
        if (!array[j]) {
            my_free_word_array(array); // <--- No need for another free here
            return NULL;
        }
        word_size = 0;
        j  ;
        while (!isprint(*i)) {
            i  ;
        }
    }
    array[j] = NULL;
    return array;
}

char **my_str_to_word_array(char const *str)
{
    char **word_array = NULL;
    size_t word_count = 0;
    if (!str) {
        return NULL;
    }
    word_count = get_words_number(str);
    word_array = malloc((word_count   1) * sizeof(char *)); // <--- You need a  1 here
    if (!word_array) {
        return NULL;
    }
    word_array = fill_array(word_array, str, word_count);
    return word_array;
}

int main(int argc, char **argv)
{
    char test[] = "My name is John Doe.\nI have 0 GPA.\nI will survive.\nThis will be removed from the string";
    *strrchr(test,'\n') = 0;
    char **word_array = my_str_to_word_array(test);
    if (word_array) {
        for (size_t i = 0; word_array[i] != NULL;   i) { // <--- Stop thrashing word_array
            printf("%s\n", word_array[i]);
        }
        printf("Test print original size %zu\n", strlen(test));
        my_free_word_array(word_array);
    }
    return 0;
}

CodePudding user response:

OP's code missed a check for a null character. @Costantino Grana

Candidate get_words_number() correction and simplification:

Count transitions from "non-word" to "word".

Use unsigned char* for defined use for all characters and is...() functions.

#include <ctype.h>
#include <stdbool.h>

size_t get_words_number(char const *str) {
  const unsigned char *ustr = (const unsigned char *) str;
  size_t count = 0;
  bool previous_not_a_word = true;
  
  while (*ustr) {
    count  = previous_not_a_word && isprint(*ustr);
    previous_not_a_word = !isprint(*ustr);
  }
  return count;
}
  • Related