1

I'm supposed to return a 2D array of string words that are common to the input strings.

#include <stdio.h>
#include <malloc.h>

#define SIZE 31

int strcmp1(char *word1, char *word2, int len) {
    while (len-- > 0) {
        if (!(*word1++ == *word2++))
            return -1;
    }
    return 0;
}

void strcpy1(char *emptyStr, char *str, int len) {
    while (len-- > 0) {
        *emptyStr++ = *str++;
    }
    *emptyStr = '\0';
}

void print(char *str) {
    for (; *str;)
        printf("%c", *str++);
    printf("\n");
}

char **commonWords(char *str1, char *str2) {
    char *temp1, *temp2;
    char commWords[][] = (char**)calloc(10, SIZE);
    int i = 0;

    if (str1 == NULL || str2 == NULL) {
        return NULL;
    }

    for (temp1 = str1; *temp1; ++temp1) {
        if (*temp1 != ' ') {
            str1 = temp1;
            while (*temp1++ != ' ')
                ;
            int len1 = temp1 - str1;
            for (temp2 = str2; *temp2; ++temp2) {
                if (*temp2 ! =' ') {
                    str2 = temp2;
                    while (*temp2++ != ' ')
                        ;
                    int len2 = temp2 - str2;

                    if (len1 == len2) {
                        if (strcmp1(str1, str2, len1)) {
                            strcpy1(commWords[i++], str1, len1);
                        }
                    }
                }
            }
        }
    }
    commWords[i] = NULL;
    return commWords;
}

int main() {
    char *name1 = "abc def ghi";
    char *name2 = "ghi abc jkl";
    char common[][31] = commonWords(name1, name2);
    int i = 0;
    while (common[i++] != NULL) {
        printf("%s\n", common[i]);
    }
}

When I compile this, I get the error: array type has incomplete element type. Since I need to allocate memory within the function, I'm using calloc to dynamically allocate at most 10 common words of length SIZE (which is defined as 31). Is there a way to not pre-declare the number of common words (i.e., the first dimension of the array)? And why am I getting this error? Is there a more elegant solution to this problem?

chqrlie
  • 131,814
  • 10
  • 121
  • 189
ritika_13
  • 51
  • 1
  • 8
  • `char commWords[][] = (char**) calloc(10, SIZE);` `char ** commWords = calloc(10, SIZE);` – unalignedmemoryaccess Aug 08 '17 at 13:21
  • Can you explain your comment? @tilz0R – ritika_13 Aug 08 '17 at 13:22
  • @tilz0R after having made the changes you mentioned, I still get a runtime error. – ritika_13 Aug 08 '17 at 13:25
  • A pointer-to-pointer is not an array, nor is it a multi-dimensional array, nor can it point to one. You should [read this](https://stackoverflow.com/questions/42094465/correctly-allocating-multi-dimensional-arrays) to clear out some common misunderstandings. – Lundin Aug 08 '17 at 14:12

2 Answers2

0

You are using it wrong.

First, if you want to have 2D dynamic array style, use pointer to pointer. Remember, this is not 2D array!

char ** commWords;

Then allocate memory for pointers:

//Allocate memory for 10 pointers to char
commWords = malloc(sizeof(*commWords) * 10); 

Then, for each word, allocate separate memory

//Allocate memory for each element for string
for (int i = 0; i < 10; i++) {
    commWords[i] = malloc(sizeof(*commWords[i]) * SIZE); //Allocate memory with length of SIZE
}

Then use allocated memory as you expected.

unalignedmemoryaccess
  • 7,246
  • 2
  • 25
  • 40
  • Aren't you using an array of pointers to declare a 2D array? I tried that. What if I don't know how many common words there are? Is there a way to not pre-declare the first dimension of the array? Even after all this, I still get a runtime error. – ritika_13 Aug 08 '17 at 13:29
  • 1
    A pointer-to-pointer is not an array, nor is it a multi-dimensional array, nor can it point to one. You should [read this](https://stackoverflow.com/questions/42094465/correctly-allocating-multi-dimensional-arrays) to clear out some common misunderstandings. – Lundin Aug 08 '17 at 14:12
  • @ritika_13 "What if I don't know how many common words there are?" You could limit the number of reported common words to 10 (or whatever). Or you could `realloc` the array of pointers to add more common words. Or you could count the number of words in one of the strings and use that as an upper bound for the number of common words. – Ian Abbott Aug 08 '17 at 14:25
0

There are many problems in your code:

  • an array of arrays, aka 2D array, is not the same as an array of pointers to arrays. Mixing the 2 is incorrect and bound to fail.

  • the definition for common in main is incorrect: char commWords[][] = (char**)calloc(10, SIZE);. You could fix it this way:

    char (*commWords)[SIZE] = calloc(sizeof(*commWords), 10);
    

    but the rest of the code is inconsistent with commWords being a 2D array and the syntax to define commonWords() to return a pointer to an array of arrays would be very complicated.

  • The while loop to print the common strings is bogus too: you increment i before using it in the printf() statement.

Here is a more classic way to achieve your goal, using an array of pointers to strings, and the standard but little used functions strspn() and strcspn():

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

void freeWords(char **arr) {
    if (arr) {
        for (size_t i = 0; arr[i]; i++) {
            free(arr[i]);
        }
        free(arr);
    }
}

/* return a NULL terminated array of strings or NULL upon memory allocation failure */
char **commonWords(const char *str1, const char *str2) {
    size_t n = 0;
    char **common = calloc(sizeof(*common), n + 1);
    size_t pos1, len1, pos2, len2;

    if (common == NULL)
        return NULL;  /* failure */

    common[0] = NULL;
    for (pos1 = 0;;) {
        pos1 += strspn(str1 + pos1, " ");
        if (str1[pos1] == '\0')
            break;
        len1 = strcspn(str1 + pos1, " ");
        for (pos2 = 0;;) {
            pos2 += strspn(str2 + pos2, " ");
            if (str2[pos2] == '\0')
                break;
            len2 = strcspn(str2 + pos2, " ");
            if (len1 == len2 && !memcmp(str1 + pos1, str2 + pos2, len1)) {
                char *w = calloc(sizeof(*w), len1 + 1);
                if (w == NULL) {
                    freeWords(common);
                    return NULL;
                }                        
                char **p = realloc(common, sizeof(*p) * (n + 2));
                if (!p) {
                    free(w);
                    freeWords(common);
                    return NULL;
                }
                common = p;
                memcpy(w, str1 + pos1, len1);
                common[n++] = w;
                common[n] = NULL;
                break;
            }
            pos2 += len2;
        }
        pos1 += len1;
    }
    return common;
}

int main(void) {
    const char *name1 = "abc def ghi";
    const char *name2 = "ghi abc jkl";
    char **common = commonWords(name1, name2);
    if (common) {
        for (size_t i = 0; common[i] != NULL; i++) {
            printf("%s\n", common[i]);
        }
        freeWords(common);
    }
    return 0;
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • The error handling in the `if (!p || !w) {` statement will break if `p` is valid, because `freeWords(common)` will be UB at that point. Also, you could return a partial list of common words before the allocation failure occurred instead of freeing everything and returning `NULL`. Something like: `if (p) common = p; if (!p || !w) { free(w); return common; }` – Ian Abbott Aug 08 '17 at 14:35
  • @IanAbbott: good catch! error handling is such a pain, especially with `realloc`'s error prone API. I amended the function to return an allocated array except upon memory allocation failure. – chqrlie Aug 08 '17 at 15:01