Pass a multidimensional char array to function by reference

Hi there!

Im trying to simplify reading from a SD card with some helper functions that I want to wrap inside a class later. Im having a hard time understanding the pointer (*) variables so im not really sure how to solve this (if its even possible in C). I want to stay away from the String class and only want to use Chars.

Here is my code so far:

#define CMD_LINE_BUFFERSIZE   20
#define CMDS_PER_LINE         3
#define SINGLE_CMD_LENGTH     6

char stringBuffer[CMD_LINE_BUFFERSIZE+1];
char cmdBuffer[CMDS_PER_LINE][SINGLE_CMD_LENGTH+1];

void readFileByLine(char *filename)
{
  int readPos = 0;
  char *cmdsDelimiter = "#";
  char *subDelimiter = ":";
  File theFile = SD.open(filename);
  if(!theFile)
  {
    Serial.println("Failed to open file.");
    return;    
  } else {
    while (theFile.available()) 
    {
      char ch = theFile.read();
      if(ch == '\n') 
      {
        // CommandString complete
        readPos = 0;
        
        splitCharArray(stringBuffer,cmdsDelimiter);
        
        Serial.println(cmdBuffer[0]);
        Serial.println(cmdBuffer[1]);
        Serial.println(cmdBuffer[2]);
        
      } else {
        stringBuffer[readPos] = ch;
        readPos++;
      }
    } 
    theFile.close();
  }
}


void splitCharArray(char *theBuffer,char *delimiter)
{
  char *token = strtok(theBuffer, delimiter);
  int tokenCounter = 0;
  while (token != 0)
  {
    strcpy(cmdsBuffer[tokenCounter],token);
    token = strtok(0, delimiter);
    tokenCounter++;
  }
}

The splitCharArray() function works but ONLY when I specify the global char cmdsBuffer inside of it. I want to pass ANY char array to the function to split the input char array into it! :slight_smile:

To clarify a bit more what I am trying to achieve:

Lets say I have 2 Char buffers

char cmdBuffer[3][7];
char subBuffer[2][4];

And I get this C String: "A:123#B:456#C:789" from the SD Card saved in stringBuffer.

I dont want to do the whole strtok() stuff over and over again (also for reusage purposes) I want to call the function splitCharArray() and pass the appropriate buffer to it to fill it. I found that I can pass a variable by reference like this char &buffer.

So I tried this:

char *splitCharArray(char *theString,char *delimiter,char &tmpBuffer)
{
  char *token = strtok(theString, delimiter);
  int tokenCounter = 0;
  while (token != 0)
  {
    strcpy(tmpBuffer[tokenCounter],token);
    token = strtok(0, delimiter);
    tokenCounter++;
  }
  return tmpBuffer;
}

But I allways get this error:

p0005_SDReadTest.ino: In function 'void readFileByLine(char*)':
p0005_SDReadTest:69: error: invalid initialization of non-const reference of type 'char&' from a temporary of type 'char ()[7]'
p0005_SDReadTest:17: error: in passing argument 3 of 'char
splitCharArray(char*, char*, char&)'
p0005_SDReadTest.ino: In function 'char* splitCharArray(char*, char*, char&)':
p0005_SDReadTest:96: error: invalid types 'char[int]' for array subscript
p0005_SDReadTest:103: error: invalid conversion from 'char' to 'char*'

Pseudo Code (it would be a charm if it would work like this):

char stringBuffer[20];
char cmdBuffer[3][7];
char subBuffer[3][2][4];

stringBuffer = "A:123#B:456#C:789";

// Extract the commands from stringBuffer save it in cmdBuffer
cmdBuffer = splitCharArray(stringBuffer,"#"); 

for(int i=0;<sizeof(cmdBuffer);i++)
{
    // Extract each command from the cmdBuffer
    subBuffer[i] = splitCharArray(cmdBuffer[i],":"); 
}

Is there a way to dynamicaly pass a char buffer to a function in C to make reusage possible?

Hope I explained my problem correctly :wink:

Thanks in advance!

I tried also:

char stringBuffer[21];
char cmdBuffer[3][6];

stringBuffer = "A:123#B:456#C:789";

// Call
splitCharArray(stringBuffer,"#",&cmdBuffer);

// Actual function
void splitCharArray(char *theBuffer,char *delimiter,char *aBuf)
{
  char *token = strtok(theBuffer, delimiter);
  int tokenCounter = 0;
  while (token != 0)
  {
    strcpy(aBuf[tokenCounter],token);
    token = strtok(0, delimiter);
    tokenCounter++;
  }
}

Get this error:

p0005_SDReadTest.ino: In function 'void readFileByLine(char*)':
p0005_SDReadTest:65: error: cannot convert 'char ()[3][7]' to 'char' for argument '3' to 'void splitCharArray(char*, char*, char*)'
p0005_SDReadTest.ino: In function 'void splitCharArray(char*, char*, char*)':
p0005_SDReadTest:90: error: invalid conversion from 'char' to 'char*'
p0005_SDReadTest:90: error: initializing argument 1 of 'char* strcpy(char*, const char*)'

:frowning:

Any ideas what Im doing wrong?

The problem is, within your function, it only has your type declaration to go by, so there's no way of knowing that it's even a multi dimentioned array, let alone try to guess at the size of each.

You could get arround it (if you know the sizes of the dimentions ) by changing it to something like
char *splitCharArray(char *theString,char *delimiter,char tmpBuffer[][7])

But it doesn't make your function so universal.

You could get arround it (if you know the sizes of the dimentions ) by changing it to something like

Or, the final argument type can be char **. Then, the dimension of the array is irrelevant. Of course, that data IS needed by the function, so you have to pass that data, too.

Thank you very much for your reply! :slight_smile:

Thanks to your answer, I changed the function as follows:

void splitCharArray(char *theBuffer,char *delimiter,char aBuf[][7])
{
  char *token = strtok(theBuffer, delimiter);
  int tokenCounter = 0;
  while (token != 0)
  {
    strcpy(aBuf[tokenCounter],token);
    token = strtok(0, delimiter);
    tokenCounter++;
  }
}

...and call it like this:

char cmdBuffer[3][7];
splitCharArray(stringBuffer,cmdsDelimiter,&cmdBuffer[0]);

This works but is not very reusable inside a class. I can just copy this snippet to each project and adapt to the project needs... its okay. :slight_smile: Although it would be nicer wrapped inside a class... :wink:

If I do need this more than once inside a project I need to size all the buffers at the same size to make this work. So Im wasting a few bytes of RAM (because it has to be the size of the maximum length of a value), right?

@PaulS:

I tried this function signature

void splitCharArray(char *theBuffer,char *delimiter,char **aBuf)

But I get an error:

p0005_SDReadTest:67: error: cannot convert 'char (*)[7]' to 'char**' for argument '3' to 'void splitCharArray(char*, char*, char**)'

How about this?

char cmdBuffer[3][6];

char stringBuffer [] = "A:123#B:456#C:789";

// prototype
void splitCharArray(char *theBuffer,char *delimiter, char (& aBuf) [3][6]);
// Actual function
void splitCharArray(char *theBuffer,char *delimiter, char (& aBuf) [3][6])
{
  char *token = strtok(theBuffer, delimiter);
  int tokenCounter = 0;
  while (token != 0)
  {
    strcpy(aBuf[tokenCounter],token);
    token = strtok(0, delimiter);
    tokenCounter++;
  }
}

void setup ()
  {
  // Call
  splitCharArray(stringBuffer,"#",cmdBuffer);
  }  // end of setup

void loop () { }

This version should work with various sizes of buffer array and protects against overwriting data if any token is longer than the row of the array set aside to contain it.

const int bufferRows = 3;
const int bufferColumns = 6;

char stringBuffer [] = "A:123#B:456#C:789";

// prototype
void splitCharArray(char *theBuffer,char *delimiter, char *aBuf, const int rows, const int columns);

// Actual function
void splitCharArray(char *theBuffer,char *delimiter, char *aBuf, const int rows, const int columns) {
  char *token = strtok(theBuffer, delimiter);
  int tokenCounter = 0;
  while (token != 0 && tokenCounter < rows) {
    strncpy(aBuf+tokenCounter*columns, token, columns);
    token = strtok(0, delimiter);
    tokenCounter++;
  }
}

void setup () {
  // Declare buffer locally
  char cmdBuffer[bufferRows][bufferColumns];
  // Call
  splitCharArray(stringBuffer, "#", &cmdBuffer[0][0], bufferRows, bufferColumns);
  }  // end of setup

void loop () { }

A char** cannot represent a 2d char array.

The second half of this article explains how to use array references, and how to pass arrays of different sizes using templates:

http://arduino.land/FAQ/content/6/32/en/passing-arrays-to-functions.html

Thanks guys for your help! :slight_smile:

The sample code of Nick works very well (johnwasser's does not return the full char string sometimes!?).

@ pYro_65

Thanks for the link you provided. A good explanation of the problem. Im very curious about the template concept. So far I understand that a class or function can be a generic one and can be defined from the caller, right? Love it :smiley: Im coming from Objective-C and C# so there are so many methods to make life easier that I need so more tools at hand for optimising my Arduino code.

I tried this to make it a template function:

template<size_t N> 
void splitCharArray(char *theBuffer,char *delimiter,char aBuf[][N])
{
  char *token = strtok(theBuffer, delimiter);
  int tokenCounter = 0;
  while (token != 0)
  {
    strcpy(aBuf[tokenCounter],token);
    token = strtok(0, delimiter);
    tokenCounter++;
  }
}

... and call it like this:

splitCharArray<7>(stringBuffer,"#",&cmdBuffer[0]);

But unfortunately I get this error:

p0005_SDReadTest:17: error: 'N' was not declared in this scope
p0005_SDReadTest.ino: In function 'void readFileByLine(char*)':
p0005_SDReadTest:66: error: ISO C++ forbids comparison between pointer and integer
p0005_SDReadTest:66: error: ISO C++ forbids comparison between pointer and integer
p0005_SDReadTest:17: error: too many arguments to function 'void splitCharArray(char*, char*)'
p0005_SDReadTest:69: error: at this point in file

Would love to get it working with the template concept! :slight_smile:

But unfortunately I get this error:
p0005_SDReadTest:17: error: 'N' was not declared in this scope

Your code looks fine (not for references, keep reading), the error is a result of an IDE hiccup. I have another article which explains why and also has a neat method of tricking the IDE which prevents it from messing things up.

http://arduino.land/FAQ/content/2/13/en/my-class_reference-won_t-work-in-the-sketch.html

For the short answer, modify the function declaration to include the exception specification throw() at the end of the function, and move the function above any others that call it:

template<size_t N> 
void splitCharArray(char *theBuffer,char *delimiter,char aBuf[][N]) throw()
{
  //...
}

Your implementation needs fixing if you wish to use references though. The function parameter you have: 'char aBuf[][N]' is still declaring a pointer type as some dimensions are omitted, and there is no & to specify a reference. To explicitly add the missing dimension, we need an extra template parameter. But before continuing, the code inside your function is not relying on the array lengths, so it is fine as it stands (untested, but probably works using pointers with the fixes for the IDE) and the template is not needed as its not used.

To use an actual array reference (to a 2d array) it would look like the code below ( BTW: a reference requires all dimensions to be specified):

template< size_t N, size_t M > 
void splitCharArray(char *theBuffer,char *delimiter,char (&aBuf)[N][M]) throw()
{
  //...
}

The reason for the brackets was mentioned in my FAQ, the array needs to be a reference, not the type of the array elements (an array of references is not allowed). Templates can also be used to get the size of an array.

So far I understand that a class or function can be a generic one and can be defined from the caller

Yes, functions can also benefit from automatic type deduction. In the case of an array (like what we are trying to do), the compiler can deduce the template parameter for us (length of array), so there is no need to explicitly specify it. In C++11 this extends to classes as well.

...and call it like this:

splitCharArray<7>(stringBuffer,"#",&cmdBuffer[0]);

To pass the multidimensional array to the function prototype I modified, you would call it like this:

splitCharArray( stringBuffer, "#", cmdBuffer );

In the call you made, the expression 'cmdBuffer[0]' is an lvalue expression and is effectively a reference to a single dimension array. When used with the & operator, the array is decayed to a pointer to its first element just like the 1D examples in my article, then the '&' returns its address.

Ask questions if I've needed, had a sleepless weekend due to my birthday and other events, so I've probably left something important out.

Dear pYro,

it works like a charm :slight_smile:

My function:

// DESC: Splits and copies a char array by delimiter into passed char array
// WARN: THIS FUNCTION NEEDS TO BE ABOVE ANY CALL IN CODE
// ARG: char *theBuffer = Source chars array
// ARG: char *delimiter = Char delimiter eg. "$"
// ARG: char (&aBuf[N][M]) = The output buffer char array specify dimensions in <N,M> before arguments
// USAGE: char cmdBuffer[3][7]
// USAGE: splitCharArray<3,7>("123$2313$431","$",cmdBuffer);
template< size_t N, size_t M > 
void splitCharArray(char *theBuffer,char *delimiter,char (&aBuf)[N][M])throw()
{
  char *token = strtok(theBuffer, delimiter);
  int tokenCounter = 0;
  while (token != 0)
  {
    strcpy(aBuf[tokenCounter],token);
    token = strtok(0, delimiter);
    tokenCounter++;
  }
}

The call:

char cmdBuffer[3][7];
splitCharArray<3,7>(stringBuffer,"#",cmdBuffer);

Hurray!

Now Im going to try pack this piece of code in a custom char helper class... hope Im getting it right. :wink:

Thanks so much for your detailed answer and explanation! :slight_smile:

Happy belated birthday btw :wink:

No worries, glad to help.

I prefer to leave out the array sizes in the call, it'll work it out for you:

char cmdBuffer[3][7];
splitCharArray(stringBuffer,"#",cmdBuffer);