0

I am new to C Programming and currently, I am writing a function that will copy the string into a string array, and returned back to its caller. I have tested the functions and everything works fine in the function. (eg: printf the string element in the function to show that it is actually stored in the array) but when i returned the string array, the output is showing nothing.

I have referred to how to return a string array from a function but the methods here does not seems to be working for me.

This is my function called

#define ROWS 4
#define WORDS_COUNT 50

char **toStrArray(char str[], char **strArr) 
{
    int i, j, iTmp;
    int len; 
    char *tmp; /* Temporary string to copy each words */

    len = strlen(str);    
    tmp = (char*)malloc(len * sizeof(char));

    j = 0; iTmp = 0;
    /* ASSERTION: Iterate through str and assign the string to strArr */
    for( i = 0; str[i] != '\0'; i++ )
    {
        if( str[i] == ' ' )
        {
            *(strArr + j) = tmp; /* Assign the copied words to strArr */
            /* printf("%s\n", *(strArr + j)); < for testing purposes only */ 
            iTmp = 0; /* Reset back to 0 */
            memset(tmp, 0, len * sizeof(char));
            j++; /* Move to next row */
        }
        else
        {
            tmp[iTmp] = str[i]; /* Copy the words in str to tmp */
            iTmp++;
        }
    }
    *(strArr + j) = tmp; /* Store the last words in the string because the last words is not end with ' ' 
                            but rather '\0' */
    /* printf("%s\n", *(strArr + j)); < for testing purposes only */

    /* Ensures no memory leak */
    free(tmp);
    tmp = NULL;
    return strArr;
}

Caller main function: Output is just ", , , ,

    char str[] = "This tea is good";
    char **arg_1;
    int i;
    /* Allocation of string array of arg_1 */
    arg_1 = (char**)malloc(ROWS * sizeof(char*));

    /* ASSERTION: Iterate through the rows of string array and allocate their memory */
    for( i = 0; i < 4; i++ )
        *arg_1 = (char*)malloc(WORDS_COUNT * sizeof(char));

    arg_1 = toStrArray(str, arg_1);

    for( i = 0; i < 4; i++ )
        printf("%s, ", *(arg_1 + i));

    printf("\n");
14
  • 2
    please always post code someone can just download and compile. And when possible also the input data for a text and some discussion of what is happening Commented Sep 5, 2020 at 14:08
  • 1
    *(strArr + j) = tmp; you always set the same (temporary) string in strArr and "erase" its content memset()ing everything to 0 few lines after. also please remember that strings in c should be terminated by \0 and check for memory leaks/problems with a tool like valgrind. Commented Sep 5, 2020 at 14:22
  • The erase of the string by memset will only affects the content in tmp right? As I have tested printf("%s\n", *(strArr + j)) and it shows the correct output. But the issues is that I could not get the string array back into my caller function Commented Sep 5, 2020 at 14:30
  • 1
    The erase of the string by memset will only affects the content in tmp right? Every strArr + j points to the same tmp. /* Assign the copied words to strArr */ is a misleading comment. This line doesn't do that. It assigns a pointer — always the same pointer, tmp. Commented Sep 5, 2020 at 14:37
  • 1
    You also do not allocate space for the null terminator and do not store said terminator in your string. Commented Sep 5, 2020 at 14:39

2 Answers 2

1

regarding;

arg_1 = toStrArray(str, arg_1);

this is overlaying the pointer to the dynamic memory that was previously allocated via calls to malloc(). This results in a unrecoverable memory leak. Suggest:

for( size_t i = 0; i < 4; i++ )
{
    strcpy( argv_1[i], str );
}

and eliminating the function: toStrArray()

If your trying to separate each word of str[] into a separate array entry suggest:

char *token = strtok( str, " ");
while( token )
{
    strcpy( arg_1[i], token );
    token = strtok( NULL, " " );
} 
Sign up to request clarification or add additional context in comments.

Comments

0

but the methods here does not seems to be working for me.

In that code the "string array" is declared as

    char sub_str[10][20]; // [1]

You declared the function maybe as

    char** toStrArray(char str[], char **strArr): // [2]

And these are different animals

In general you do not mix [] and ** notation. You can declare just

   char** toStrArray(char* str, char** strArr); // [3]

The returned value would be the same as strArr. The reason for that is the convenience of being able to use the function call in an expression. See fgets() for example.

But

  • neither of the two are array of string in itself

  • [1] is a block of 10 x 20 char starting at address sub_str

     char sub_str[10][20];
    

and it will be an array of strings only if each of the 10 20-byte segments are null-terminated. At first it is just a block of char

  • [2] is a pointer to a pointer to char

      char** strArray;
    
  • *strArray is a pointer to char

  • **strArray is a char

  • and it will never be an array of char. But it can be an array of pointers to char and it would be the most flexible use since we could then iterate over the pointers pointed by **strArray, writing code like *strArray[0], *srtArray[1] and so on. An array of strings

in both cases the "arrays" must be constructed carefully or you will get no array, no strings, leak memory or crash your program

I will show an example of each case

The first case maybe somewhat off-topic but I think it makes sense in order to compare things often confused

building sub_str[10][20]

Here we have just an area of 10*20*sizeof(char) bytes. There is no such thing as multidimensional arrays in C. Each 20-byte segment are just laid down one after another starting at &sub_str.

Since each string is null terminated we can have up to 10 strings up to 19 bytes each in size. Here is a C program that fills such a block

#include <stdio.h>
int main(void)
{
    char    sub_str[10][20];

    for (int i = 0; i < 10; i += 1)
        sprintf(
            sub_str[i],
            "%16s%03d",
            "This is string ", i+1
        );

    for (int i = 0; i < 10; i += 1)
        printf(
            "%2d  %s len(%zd)\n",
            i, sub_str[i], strlen(sub_str[i])
        );
    return 0;
};

This is the output

0   This is string 001 len(19)
1   This is string 002 len(19)
2   This is string 003 len(19)
3   This is string 004 len(19)
4   This is string 005 len(19)
5   This is string 006 len(19)
6   This is string 007 len(19)
7   This is string 008 len(19)
8   This is string 009 len(19)
9   This is string 010 len(19)

And we have an array of strings. See the printf() iterating accessing the i-th one below

    printf(
    "%2d  %s len(%zd)\n",
    i, sub_str[i], strlen(sub_str[i])
);

Problem here is that we have little flexibility and possibly a lot of wasted memory. All sizes are fixed, static

Building char** strArray; finally

This is the most flexible and familiar way, since every C program uses at least one case. main() has the common prototype

int main(int argc, char** argv);

And here we see an all-important thing missing in your program: since srtArray is just a pointer, you need to know to how many pointers it points to. By itself *strArray is just a pointer to char. And only if it is allocated. By declaring char** strArr; you get just a pointer, and in fact it would the safe to write

        char** strArr = NULL;

Consider this

typedef struct
{
    int     argc;
    char**  argv;
}   stringArray;

And now the ends meet. In fact you could have declared

      void toStrArray(char* str, stringArray strArr);

and pack the argc value into the struct after all

About the program: the code just mimics what the system does for main(), building a list of strings from the command line parameters, even including the program name as the fist one. And then calls this function

    int just_like_main(int argc, char** argv)

that prints out the arguments.

It does what you must do in your function.

Note that you must have a way to control the number of strings in your array, at all times, just like argc in main(). It is essential to have this in mind.

Here is the code

#define _BLOCK_ 4

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

typedef struct
{
    int     argc;
    char** argv;
}   stringArray;

int just_like_main(int, char**);

int main(void)
{
    const int   n_parm = 16;
    const char* test_strings[16] =
    {
        "Creating", "a", "block", "of",
        "strings", ",", "just", "like",
        "the", "system", "does", "for" ,
        "main()", "for", "every", "program"
    };

    stringArray ex; // example
    int N = _BLOCK_; // 1st block
    ex.argc = 0;
    ex.argv = (char**)malloc(sizeof(char*) * _BLOCK_);
    const char* program_name = "full path of program";
    ex.argv[ex.argc] = (char*)malloc(1 + strlen(program_name));
    strcpy(ex.argv[ex.argc], program_name);
    ex.argc += 1; // 1st arg. That was easy

    // loads all other strings into **argv
    for (int i = 0; i < n_parm; i += 1)
    {   // each one
        if (ex.argc >= N)
        {   // block is full
            N = N + _BLOCK_;
            char* new_block = realloc(ex.argv, (N * sizeof(char*)));
            printf("Block extended for a total of %d pointers\n", N);
            ex.argv = (char**)new_block;
        };
        ex.argv[ex.argc] = (char*)malloc(1 + strlen(test_strings[i]));
        strcpy(ex.argv[ex.argc], test_strings[i]);
        ex.argc += 1;
    };  // for()
    printf("\t%d strings in the block:\n", ex.argc);
    for (int i = 0; i < ex.argc; i += 1)
    {
        printf("\t\t%d of %d: '%s'\n", 1 + i, ex.argc, ex.argv[i]);
    };

    // now trims the end of the block
    // allocated: N.
    // used: argc
    printf("\t%d pointers allocated\n", N);
    printf("\t%d arguments read\n", ex.argc);
    if (N == ex.argc)
        printf("\tNothing to free()\n");
    else
    {
        printf("\t%d pointers to free\n", N - ex.argc);
        char* new_size = realloc(ex.argv, (ex.argc * sizeof(char*)));
        printf("\tBlock size trimmed for a total of %d pointers\n", ex.argc);
        ex.argv = (char**)new_size;
    };
    printf("\tCalling just_like_main() with these args\n");
    int res = just_like_main(ex.argc, ex.argv);
    printf("\n\n\t\"main()\" returned %d\n", res);
    printf("\tNow fres the block and exit\n");
    for (int i = 0; i < ex.argc; i += 1)
        free(ex.argv[i]);
    free(ex.argv);
    printf("\n\nGame over\n");
    return 0;
};

int just_like_main(int argc, char** argv)
{
    printf("\n\tAt \"main()\": %d arguments\n\n", argc);
    for (int i = 0; i < argc; i += 1)
        printf("%8d\t'%s'\n", i, argv[i]);
    return 0;
};  // just_like_main()

And the output

Block extended for a total of 8 pointers
Block extended for a total of 12 pointers
Block extended for a total of 16 pointers
Block extended for a total of 20 pointers
        17 strings in the block:
                1 of 17: 'full path of program'
                2 of 17: 'Creating'
                3 of 17: 'a'
                4 of 17: 'block'
                5 of 17: 'of'
                6 of 17: 'strings'
                7 of 17: ','
                8 of 17: 'just'
                9 of 17: 'like'
                10 of 17: 'the'
                11 of 17: 'system'
                12 of 17: 'does'
                13 of 17: 'for'
                14 of 17: 'main()'
                15 of 17: 'for'
                16 of 17: 'every'
                17 of 17: 'program'
        20 pointers allocated
        17 arguments read
        3 pointers to free
        Block size trimmed for a total of 17 pointers
        Calling just_like_main() with these args

        At "main()": 17 arguments

       0        'full path of program'
       1        'Creating'
       2        'a'
       3        'block'
       4        'of'
       5        'strings'
       6        ','
       7        'just'
       8        'like'
       9        'the'
      10        'system'
      11        'does'
      12        'for'
      13        'main()'
      14        'for'
      15        'every'
      16        'program'


        "main()" returned 0
        Now free() the block and exit

Game over

About the logic

Memory will be allocated in blocks of size _BLOCK_ pointers. Anytime memory runs out a new block of the same size is allocated. At exit the complete block is trimmed to the used size and the rest is 'free()'

Ex: for 12 strings and _BLOCK_ of 5 3 blocks will be allocated for a total of 15. At the end the last 3 pointers are freed so the function returns a block of just 12 pointers, as expected.

In practical applications we must choose this value carefully in order to not allocate very much AND not allocate very often :)

Just for fun the first parameter will be the name of the program and a function declared

     int just_like_main(int argc, char** argv);

will be called and it just displays the full parameter block

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.