1

I have a function that manipulates a char*** using malloc and memcpy this way

// Convert a buffer full line to separated variables
int parseBufferToVariables(char ***variableContainer, char *bufferToParse, int maxVarSize) {
    int i = 0;
    // Get number of rows of the string
    int numberOfRows = 0;
    for (i = 0; bufferToParse[i] != '\0'; i++) {
        if (bufferToParse[i] == '\n')
            ++numberOfRows;
    }
    // Get number of columns of the string
    int numberOfColumns = 1;
    for (i = 0; bufferToParse[i] != '\n'; i++) {
        if (bufferToParse[i] == '\t')
            ++numberOfColumns;
    }
    // Allocate separated variable array
    size_t dim0 = numberOfColumns, dim1 = numberOfRows, dim2 = maxVarSize;
    variableContainer = malloc(sizeof *variableContainer * dim0);
    if (variableContainer) {
        size_t i;
        for (i = 0; i < dim0; i++) {
            variableContainer[i] = malloc(sizeof *variableContainer[i] * dim1);
            if (variableContainer[i]) {
                size_t j;
                for (j = 0; j < dim1; j++) {
                    variableContainer[i][j] = malloc(sizeof *variableContainer[i][j] * dim2);
                }
            }
        }
    }
    // Start parsing string to 3D array
    int init            = 0;
    int numberOfVars    = 0;
    int numberOfLines   = 0;
    int sizeOfVar       = 0;
    int position        = 0;
    char emptyArray[MAXVARSIZE] = {0};
    // Loop trought all lines
    i = 0;
    while (numberOfLines  < numberOfRows) {
        // Every delimiter
        if (bufferToParse[i] == '\t' || bufferToParse[i] == '\n') {
            // Size of the new sring
            sizeOfVar = i - init;
            // Set last \0 character in order to recognize as a proper string
            memcpy(&variableContainer[numberOfVars][numberOfLines], emptyArray, maxVarSize);
            // Copy the string to array
            memcpy(&variableContainer[numberOfVars][numberOfLines], &bufferToParse[position], sizeOfVar);
            // Handle pointers poisition
            init = i + 1;
            position += sizeOfVar + 1;
            // Handle when end of line is reached
            if (bufferToParse[i] == '\n') {
                numberOfVars = 0;
                numberOfLines++;
            }
        }
        i++;
    }
    return numberOfRows;
}

And Im trying to call it in different ways:

char*** container= {0};
parseBufferToVariables (&container, inputString, MAXVARSIZE);

char*** container= {0};
parseBufferToVariables (container, inputString, MAXVARSIZE);

Even I try calling a char**** in the function:

int parseBufferToVariables(char**** variableContainer, char* bufferToParse, int maxVarSize)

But I always have a seg-fault calling the char*** outside the parseBufferToVariables function. Any ideas?

chqrlie
  • 131,814
  • 10
  • 121
  • 189
Keles
  • 369
  • 1
  • 2
  • 14
  • Four star programming, nice :) Please study [Correctly allocating multi-dimensional arrays](https://stackoverflow.com/questions/42094465/correctly-allocating-multi-dimensional-arrays). – Lundin May 06 '20 at 13:34
  • 1
    In general: if you have three stars `***`, you are probably doing something wrong. If you have four stars `****`, you are __certainly__ doing something wrong. – Jabberwocky May 06 '20 at 13:47
  • A problem with this approach is that the `numberOfColumns` info is lost on return. The calling code lacks info to know how far to index into `container[][]`. – chux - Reinstate Monica May 06 '20 at 14:43
  • Any similar approach to generate 3D array? (im making this with pointers because the string is around 1gb) – Keles May 06 '20 at 14:47

2 Answers2

2

OP is shooting for a 4 * parameter, yet other approaches are better.

The high degree of *s mask a key failing is that code needs to convey the column (# of tabs) width somehow.

Further, I see no certain null character termination in forming the _strings_as the 2nd memcpy() is unbounded in size - may even overwrite allocation boundaries.


The idea below is that each level of allocation ends with a null.

csv = parse_file_string(const char *file_string);

Upon return, when csv[line] == NULL, there are no more lines

When csv[line][tab] == NULL, there are no more strings.

This approach also allows for a different number of strings per line.

Adjusted algorithm, pseudo C code

// return NULL on error
char ***parse_file_string(const char *file_string) {
  number_lines = find_line_count(file_string);
  char ***csv = calloc(number_lines + 1, sizeof *csv);
  if (csv == NULL) return NULL;

  for (line=0; line < number_lines; line++) {
    tab_count = find_tab_count(file_string);
    csv[line] = calloc(tab_count + 2, sizeof *(csv[line])); 
    // add NULL check

    for (tab=0; tab < tab_count; tab++) {
      char *end = strchr(file_string, '\t');
      csv[line][tab] = malloc_string(file_string, end);
      // add NULL check
      file_string = end + 1;
    }
    char *end = strchr(file_string, '\n');
    csv[line][tab++] = malloc_str(file_string, end); 
    // add NULL check
    file_string = end + 1;
    csv[line][tab] = NULL;

  }
  csv[line] = NULL;
  return csv;
}

Usage

char ***container = parse_file_string(file_string);

for (line=0; container[line]; line++)
  for (tab=0; container[line][tab]; tab++)
     puts(container[line][tab]);

//free
for (line=0; container[line]; line++)
  for (tab=0; container[line][tab]; tab++)
     free(container[line][tab]);
  free(container[line]);
free (container)
chux - Reinstate Monica
  • 143,097
  • 13
  • 135
  • 256
1

A pointer to a variable length array could be used if supported.
First get the dimensions of the contents of the buffer. This assumes that each line will have the same number of tabs.
Declare the pointer and allocate the memory.
Then parse the buffer into the allocated memory.

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

void getdimension ( char *buffer, int *rows, int *cols, int *size) {
    int maxsize = 0;
    *rows = 0;
    *cols = 0;
    *size = 0;
    while ( *buffer) {//not the terminating zero
        if ( '\n' == *buffer) {
            if ( ! *rows) {//no rows counted yet
                ++*cols;//add a column
            }
            ++*rows;
            if ( maxsize > *size) {
                *size = maxsize;
            }
            maxsize = 0;
        }
        if ( '\t' == *buffer) {
            if ( ! *rows) {//no rows counted yet
                ++*cols;
            }
            if ( maxsize > *size) {
                *size = maxsize;
            }
            maxsize = 0;
        }
        ++maxsize;
        ++buffer;
    }
    if ( '\n' != *(buffer - 1)) {//last character is not a newline
        ++*rows;
        if ( maxsize > *size) {
            *size = maxsize;
        }
    }
}

void createptr ( int rows, int columns, int size, char (**ptr)[columns][size]) {
    if ( NULL == ( *ptr = malloc ( sizeof **ptr * rows))) {
        fprintf ( stderr, "malloc problem\n");
        exit ( EXIT_FAILURE);
    }
    for ( int line = 0; line < rows; ++line) {
        for ( int tab = 0; tab < columns; ++tab) {
            (*ptr)[line][tab][0] = 0;
        }
    }
}

void parsebuffer ( char *buffer, int rows, int columns, int size, char (*ptr)[columns][size]) {
    int eachrow = 0;
    int eachcol = 0;
    int eachsize = 0;

    while ( *buffer) {
        if ( '\n' == *buffer) {
            ++eachrow;
            eachcol = 0;
            eachsize = 0;
        }
        else if ( '\t' == *buffer) {
            ++eachcol;
            eachsize = 0;
        }
        else {
            ptr[eachrow][eachcol][eachsize] = *buffer;
            ++eachsize;
            ptr[eachrow][eachcol][eachsize] = 0;
        }
        ++buffer;
    }
}

int main ( void) {
    char line[] = "12\t34\t56\t78\t!@#\n"
    "abc\tdef\tghi\tjkl\t$%^\n"
    "mno\tpqr\tstu\tvwx\tyz\n"
    "ABC\tDEF\tGHI\tJKL\tMNOPQ\n";
    int rows = 0;
    int columns = 0;
    int size = 0;

    getdimension ( line, &rows, &columns, &size);
    printf ( "rows %d cols %d size %d\n", rows, columns, size);

    char (*ptr)[columns][size] = NULL;//pointer to variable length array

    createptr ( rows, columns, size, &ptr);

    parsebuffer ( line, rows, columns, size, ptr);

    for ( int row = 0; row < rows; ++row) {
        for ( int col = 0; col < columns; ++col) {
            printf ( "ptr[%d][%d] %s\n", row, col, ptr[row][col]);
        }
    }

    free ( ptr);

    return 0;
}
user3121023
  • 8,181
  • 5
  • 18
  • 16