简体   繁体   中英

Returning struct of struct by a C function

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)

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;
}

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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM