简体   繁体   中英

Transform 1D char array to 2D char array without allocating memory

I have a string, for example:

char* cmd = "a bcd ef hijk lmmopq";

The string is composed with segments split by space, the number of segments is not fixed.

Intuitively, I can get a 2D char string by allocating memory dynamically, for example:

char** argv = malloc();
char* argv[0] = malloc();
...
char* argv[i] = malloc();

But can I transform the original array to 2d char array like below to avoid memory allocation?

char* argv[] = {"a", "bcd", "ef", "hijk", "lmmopq"};  

As pointed out in another answer, strtok can be used to split up your string in-place so that delimiters (spaces) are replaced with null terminators.

To know how many strings there will be, you'll have to iterate through the string twice. For the first iteration, invent some quick & simple function that doesn't alter the string, like this:

size_t count_spaces (const char* src)
{
  size_t spaces=0;
  for(; *src != '\0'; src++)
  {
    if(*src == ' ')
    {
      spaces++;
    }
  }
  return spaces;
}

Then for the second iteration, use strtok . Full example:

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

size_t count_spaces (const char* src)
{
  size_t spaces=0;
  for(; *src != '\0'; src++)
  {
    if(*src == ' ')
    {
      spaces++;
    }
  }
  return spaces;
}

void tokenize (char* restrict src, 
               size_t dst_size, 
               char* restrict dst [dst_size])
{
  size_t i;
  char* ptr = strtok(src, " ");
  for(i=0; i<dst_size && ptr != NULL; i++)
  {
    dst[i] = ptr;
    ptr = strtok(NULL, " ");
  }
}

int main (void) 
{
  char str [] = "a bcd ef hijk lmmopq";
  size_t size = count_spaces(str) + 1;
  char* ptr_arr [size];

  tokenize(str, size, ptr_arr);

  for(size_t i=0; i<size; i++)
  {
    puts(ptr_arr[i]);
  }
}

Continuing from my comment, you can tokenize your string with, eg strtok and assign the pointers to the individual words to a pointer-to-pointer-to-char . For example:

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

#define MAX 10

int main (void) {

    char *cmd = (char[]){"a bcd ef hijk lmmopq"},       /* compound literal */
    // char cmd[] = "a bcd ef hijk lmmopq";   /* otherwise declare as array */
        *arr[MAX] = {NULL},
        *delim = " \n";
    size_t n = 0;

    for (char *p = strtok (cmd, delim); n < MAX && p; p = strtok (NULL, delim))
        arr[n++] = p;

    for (int i = 0; i < (int)n; i++)
        printf ("arr[%d]: %s\n", i, arr[i]);

    return 0;
}

*Example Use/Output**

$./bin/str2ptp
arr[0]: a
arr[1]: bcd
arr[2]: ef
arr[3]: hijk
arr[4]: lmmopq

Note: You cannot pass a string literal to strtok as strtok modifies the string. Either use a pointer to an array or declare and initialize as a normal char[] array to begin with.


Dynamically allocating pointer for unknown number of words

If you have no idea whether you could read twenty words or 2000 words, you can easily handle the situation by dynamically allocating blocks of pointers, and then realloc ating again if the prior max allocation is again reached. It is a simple process, eg

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

#define MAX 10
#define MAXB 4096

int main (void) {

    char cmd[MAXB] = "",  /* buffer of 4096 chars to hold each line input */
        **arr = calloc (MAX, sizeof *arr),
        *delim = " \n";
    size_t n = 0, max = MAX;

    while (fgets (cmd, MAXB, stdin)) { /* read each line of input on stdin */

        size_t len = strlen (cmd);  /* get line length */
        if (cmd[len - 1] == '\n')   /* test for trailing '\n'  */
            cmd[--len] = 0;         /* overwrite with nul-byte */

        for (char *p = strtok (cmd, delim); p; p = strtok (NULL, delim)) {
            arr[n++] = p;

            if (n == max) { /* realloc arr by MAX if n == MAX */
                void *tmp = realloc (arr, (max + MAX) * sizeof *arr);
                if (!tmp) {
                    fprintf (stderr, "error: memory exhausted.\n");
                    break;
                }
                arr = tmp;  /* zero new pointers (optional) */
                memset (arr + max, 0, MAX * sizeof *arr);
                max += MAX; /* update current max iteration */
            }
        }
        for (int i = 0; i < (int)n; i++)
            printf ("arr[%2d]: %s\n", i, arr[i]);
    }

    free (arr);  /* don't forget, if you allocate it -> you free it. */

    return 0;
}

Above, always validate your allocations with, eg if (!arr) { /* handle error */ } which was omitted from the initial allocation of arr for brevity.

Example Use/Input

$ echo "A quick brown fox jumps over the lazy dog while the dog watched" | \
./bin/strtop2pdyn
arr[ 0]: A
arr[ 1]: quick
arr[ 2]: brown
arr[ 3]: fox
arr[ 4]: jumps
arr[ 5]: over
arr[ 6]: the
arr[ 7]: lazy
arr[ 8]: dog
arr[ 9]: while
arr[10]: the
arr[11]: dog
arr[12]: watched

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