Home > database >  Returning struct of struct by a C function
Returning struct of struct by a C function

Time:09-17

I tried to create a simple split function to return elements as a struct, and used another struct to return the results and the number of elements.

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

struct TOKENS
{
  int length;
  char word[];
};

struct OUTPUT
{
  struct TOKENS *a;
  int l;
};

struct OUTPUT explode(char *a, char *b)
{
  struct TOKENS tokens[10];
  int i = 0;
  char *ptr = strtok(a, b);
  while (ptr != NULL)
  {
    tokens[i].length = strlen(ptr);
    strcpy(tokens[i].word, ptr);
    ptr = strtok(NULL, b);
    i  ;
  }
  struct OUTPUT r = {
      .a = &tokens[0], .l = i};
  return r;
}

int main()
{
  char str[] = "This is test for the start";
  struct OUTPUT r = explode(str, " ");
  struct TOKENS *tokens = r.a;
  printf("%d\n", r.l);
  for (int i = 0; i < r.l; i  )
  {
    printf("%d %s (%d)\n", i, tokens[i].word, tokens[i].length);
  }

  return 0;
}

However, the output is not what I aimed for:

6
0  (4)
1  (2)
2  (4)
3  (3)
4  (3)
5 start (5)

CodePudding user response:

As mentioned in the comments, you have to use dynamic allocation when using a structure with a flexible array member. The size of the structure doesn't include the array size, you have to add the size of the array when calling malloc().

This means you can't have an array of TOKENS, so you need OUTPUT.a to be an array of pointers. Use malloc() and realloc() to allocate the memory for r.a, and use malloc() to allocate the memory for each TOKENS element.

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

struct TOKENS
{
    int length;
    char word[];
};

struct OUTPUT
{
    struct TOKENS **a;
    int l;
};

struct OUTPUT explode(char *a, char *b)
{
    struct OUTPUT r = {.a = NULL, .l = 0};
    int i = 0;
    char *ptr = strtok(a, b);
    while (ptr != NULL)
    {
        r.l  ;
        r.a = realloc(r.a, r.l * sizeof(*r.a));
        struct TOKENS *token = malloc(sizeof(*token)    strlen(ptr)   1);
        token->length = strlen(ptr);
        strcpy(token->word, ptr);
        r.a[i] = token;
        ptr = strtok(NULL, b);
        i  ;
    }
    return r;
}

int main()
{
    char str[] = "This is test for the start";
    struct OUTPUT r = explode(str, " ");
    struct TOKENS **tokens = r.a;
    printf("%d\n", r.l);
    for (int i = 0; i < r.l; i  )
    {
        printf("%d %s (%d)\n", i, tokens[i]->word, tokens[i]->length);
    }
    for (int i = 0; i < r.l; i  ) {
        free(tokens[i]);
    }
    free(tokens);

    return 0;
}

CodePudding user response:

FAM's may be useful in some instances, but the old reliable malloc() (and its user strdup(), if available) can be used here.

realloc() is used to start and to grow array of ptrs to individual 'token' structs

#include <stdio.h>
#include <stdlib.h> // for `exit()`
#include <string.h>

typedef struct TOKENS {
    int length;
    char *word; // NB: pointer, not FAM
} TOKENS_t;

typedef struct {
    TOKENS_t *a;
    int l;
} OUTPUT_t;

#define USE_SRC_STR
// or not defined in order to use dynamic allocation of "word" copies

// return ptr to struct instead of entire struct
OUTPUT_t *explode( char *a, char *b ) {
    OUTPUT_t *r = calloc( 1, sizeof *r ); // initially all bytes = 0
    // test for NULL return omitted for brevity

    // only one invocation of strtok()
    for( char *ptr = a; (ptr = strtok( ptr, b )) != NULL; ptr = NULL ) {

        // cautiously growing array of pointers
        TOKENS_t *tmp = realloc( r->a, (r->l   1) * sizeof *tmp );
        if( tmp == NULL ) {
            fprintf( stderr, "realloc failed on %d\n", r->l   1 );
            exit( EXIT_FAILURE );
        }
        // preserve "word"
#ifdef USE_SRC_STR
        tmp[ r->l ].word = ptr;
#else
        tmp[ r->l ].word = strdup( ptr );
#endif
        tmp[ r->l ].length = strlen( ptr ); // preserve length of "word"
        r->a = tmp; // realloc() may have relocated extended array
        r->l  ; // got one more
    }

    return r;
}

int main() {
    char str[] = "This is test for the start";

    OUTPUT_t *r = explode( str, " " ); // capture returned pointer

    printf( "%d\n", r->l );
    for( int i = 0; i < r->l; i   )
        printf( "%d: %s (%d)\n", i, r->a[i].word, r->a[i].length );

#ifndef USE_SRC_STR
    // free those buffers containing copies of "word"s
    while( (r->l)-- )
        free( r->a[ r->l ].word );
#endif

    free( r ); // free this, too!

    return 0;
}

Output

6
0: This (4)
1: is (2)
2: test (4)
3: for (3)
4: the (3)
5: start (5)

Or, below is another alternative where the caller passes the address of a 'top level' struct to the function (that now just does its job and returns nothing.)

void explode( OUTPUT_t *r, char *a, char *b ) {
    for( char *ptr = a; (ptr = strtok( ptr, b )) != NULL; ptr = NULL ) {
        TOKENS_t *tmp = (TOKENS_t *)realloc( r->a, (r->l   1) * sizeof *tmp );
        if( tmp == NULL ) {
            fprintf( stderr, "realloc failed on %d\n", r->l   1 );
            exit( EXIT_FAILURE );
        }
        tmp[ r->l ].word = ptr; // using original string
        tmp[ r->l ].length = strlen( ptr );
        r->a = tmp;
        r->l  ;
    }
}

int main() {
    char str[] = "This is test for the start";

    OUTPUT_t r = { 0 }; // define initialised top level instance
    explode( &r, str, " " ); // pass into "helper" function

    printf( "%d\n", r.l );
    for( int i = 0; i < r.l; i   )
        printf( "%d: %s (%d)\n", i, r.a[i].word, r.a[i].length );

    free( r.a ); // don't forget to "clean up"

    return 0;
}
  •  Tags:  
  • c
  • Related