cvalgrind

Segmentation fault when converting char * to char **


I'm trying to split a sentence (char *) to an array of words (char **). The problem is that my function that does just that sometimes doesn't return a valid char **.

char **get_words(char *buffer, char delimiter)
{
    char **words = malloc(sizeof(char *) * 4096);
    for (int i = 0; i < 4096; i++)
        words[i] = malloc(sizeof(char) * 4096);
    int word_count = 0;
    int l = 0;
    for (int i = 0; buffer[i] != '\0' && buffer[i]  != '\n'; i++, l++) {
        if (buffer[i] == delimiter) {
            words[word_count][l] = '\0';
            word_count++;
            l = -1;
        }
        else
            words[word_count][l] = buffer[i];
    }
    words[word_count][l] = '\0';
    return (words);
}

I first use it like this:

char *buffer = malloc(sizeof(char) * 50);
buffer = "/login test\n";
char **words = get_words(buffer, ' ');
printf("Words[0] = %s", words[0]);

And it works fine.

However when I do it the same way with this:

char **reply = get_words("502 Command doesn't exist.\n", ' ')

I can't even print reply[0][0] (see below) without having a segmentation fault. Moreover, I tried to debug this using valgrind but when I use it the program doesn't crash and everything works so I can't find what's wrong.

printf("Reply[0][0] = %d\n", reply[0][0]);

printf("Reply[0][0] = %c\n", reply[0][0]);

EDIT: Here is a reproductible example.

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <assert.h>

char **get_words(char *buffer, char delimiter)
{
    printf("buffer = %s\n", buffer);
    char **words = malloc(sizeof(char *) * 100);
    if (words == NULL) {
        printf("Malloc Error\n");
        exit(84);
    }
    for (int i = 0; i < 100; i++) {
        words[i] = malloc(sizeof(char) * 100);
        if (words[i] == NULL) {
            printf("Malloc Error\n");
            exit(84);
        }
    }
    int word_count = 0;
    int l = 0;
    for (int i = 0; buffer[i] != '\0' && buffer[i]  != '\n'; i++, l++) {
        if (buffer[i] == delimiter) {
            words[word_count][l] = '\0';
            word_count++;
            l = -1;
        }
        else
            words[word_count][l] = buffer[i];
    }
    words[word_count][l] = '\0';
    return (words);
}

int main()
{
    char *buffer = malloc(sizeof(char) * 100);
    buffer = "hello world !\n";
    char **words = get_words(buffer, ' ');
    printf("words[0]= %s\n", words[0]);
    free (buffer);
    char **reply = get_words("Second call\n", ' ');
    printf("reply[0] = %s\n", reply[0]);
}

Solution

  • Using a static analyzer, the first interesting and important warning is the following warning: The malloc function is not declared. Passing data to or from this function can be affected.

    Without declaring the malloc function, the program runs in a strange way. According to the C language, if a function is not declared, it returns int. But actually, it's a pointer. Let's fix this problem by adding #include <stdlib.h>.

    Now the analyzer issues another warning — we get a more serious issue: 43:1: note: V773 The 'buffer' pointer was assigned values twice without releasing the memory. A memory leak is possible.

    The issue is in the following code fragment:

    char *buffer = malloc(sizeof(char) * 100);
    buffer = "hello world !\n";
    ....
    free (buffer);
    

    The pointer value is overwritten. To copy a string to the buffer, a programmer should use special functions, for example strcpy. Let's fix this.

    Here's the fixed code.

    #include <unistd.h>
    #include <stdio.h>
    #include <string.h>
    #include <fcntl.h>
    #include <assert.h>
    #include <stdlib.h>
    
    char **get_words(char *buffer, char delimiter)
    {
        printf("buffer = %s\n", buffer);
        char **words = malloc(sizeof(char *) * 100);
        if (words == NULL) {
            printf("Malloc Error\n");
            exit(84);
        }
        for (int i = 0; i < 100; i++) {
            words[i] = malloc(sizeof(char) * 100);
            if (words[i] == NULL) {
                printf("Malloc Error\n");
                exit(84);
            }
        }
        int word_count = 0;
        int l = 0;
        for (int i = 0; buffer[i] != '\0' && buffer[i]  != '\n'; i++, l++) {
            if (buffer[i] == delimiter) {
                words[word_count][l] = '\0';
                word_count++;
                l = -1;
            }
            else
                words[word_count][l] = buffer[i];
        }
        words[word_count][l] = '\0';
        return (words);
    }
    
    int main()
    {
        char *buffer = malloc(sizeof(char) * 100);
        if (buffer == NULL)
            exit(84);
        strcpy(buffer, "hello world !\n");
        char **words = get_words(buffer, ' ');
        printf("words[0]= %s\n", words[0]);
        free (buffer);
        char **reply = get_words("Second call\n", ' ');
        printf("reply[0] = %s\n", reply[0]);
    }
    

    I can't say that this code is perfect and secure, but it runs. So, using static analyzers to find errors, you can improve your learning process.