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:
If in my test
main()
I have aprintf()
below the call to my_str_to_word_array, the format passed toprintf()
will be included in the array. So I conclude that there is a memory read error.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:
get_words_number
goes out of bounds (off by one) and may read arbitrary memory after your string (check with the example I included inmain
).- You need an additional slot in your array to put there a terminating
NULL
. - Stop thrashing your input pointer if you later need it (both in
my_free_word_array
and in the printing loop inmain
).
Suggestions:
- Next time make a Minimal, Reproducible Example by including all required headers;
strndup
is not standard (unless you have__STDC_ALLOC_LIB__
and define__STDC_WANT_LIB_EXT2__
to 1).- 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;
}