Initialize Multi Dimensional char Array with variables

I'm having trouble figuring out how to assign a character array within another multi dimensional character array during initialization. As a work around, I am just doing a strcpy which works after initialization. But, what is the best way to use the variables while initializing without the strcpy?

void setup() {
  Serial.begin(115200);
  delay(250);
  char helloString[11] = "hello";
  char goodbyeString[11] = "goodbye";
  char nnString[11] = "nightnight";

 
  char myArray[4][2][11] = { {"first",helloString[0] },
                        {"second",*goodbyeString },
                        {"third","hi" },
                        {"forth",*nnString }};
  
  //only way I could get this to work
  strcpy(myArray[3][1], nnString);

  Serial.printf("output\n");
  Serial.printf("1 %s\n", myArray[0][1]);
  Serial.printf("2 %s\n", myArray[1][1]);
  Serial.printf("3 %s\n", myArray[2][1]);
  Serial.printf("4 %s\n", myArray[3][1]);

}

void loop() {

}


output
1 h
2 g
3 hi
4 nightnight

I think that this is what you actually wanted:

    const char *helloString = "hello";
    const char *goodbyeString = "goodbye";
    const char *nnString = "nightnight";

    const char *myArray[4][2] = {   {"first",helloString },
                                    {"second",goodbyeString },
                                    {"third","hi" },
                                    {"forth",nnString } };

    Serial.printf("output\n");
    Serial.printf("1 %s\n", myArray[0][1]);
    Serial.printf("2 %s\n", myArray[1][1]);
    Serial.printf("3 %s\n", myArray[2][1]);
    Serial.printf("4 %s\n", myArray[3][1]);

May be an array of array is not the best approach.

What about a structure:

// Define the RankWord structure
struct {
  const char * rank;
  const char * text;
} RankWord;

// now have an Array of RankWord type
RankWord rankedWords[] {
  {"first" , "hello"},
  {"second", "goodbye"},
  {"third" , "hi"},
  {"fourth", "nightnight"},
};
// calculate the number of entries
const size_t  numRankedWords = sizeof rankedWords / sizeof * rankedWords;

and now if you want to duplicate an entry, you use a Data type

Data copyOfARankedWord = rankedWords[2]; 

and to access the content

void printData(Data& aRankedWord) {
  Serial.print(F("Rank: "));  Serial.println(aRankedWord.rank);
  Serial.print(F("Text: "));  Serial.println(aRankedWord.text);
}

(all typed here so might have some typos)

makes sense ?

But I want to be able to change the variables within the multi dimensional array later in the code so const char wouldn't work.

Good idea. I will try this out. Thanks.

I am not an advanced programmer. I found using multi-dimension arrays helpful and understandable. Trying to pass multi-dimension arrays to functions (by type and by reference) was difficult, confused me and the compiler complained. I read that this is one of the problems with coding security as it leaves memory open to attack. You even mention one of the hurdles I tripped over - const char (which can not be passed)... so I sacked the whole multi-dimension and went with flat, single-dimension arrays, and let math find what I want (I can pretend a 100-char array is 10 arrays of 10-char arrays). Here is my scratchpad program that let me see the light in dumping multi-dimension arrays. See notes and references in the comments. Good luck.

// https://arduinogetstarted.com/faq/how-to-pass-array-to-function-in-arduino
// https://academy.programmingelectronics.com/pass-array-to-function-in-arduino/
// https://wokwi.com/projects/353414726659848193

int array_1[] = {1, 2};
int length_1  = sizeof(array_1) / sizeof(array_1[0]);
int array_2[] = {3, 4, 5, 6};
int length_2  = sizeof(array_2) / sizeof(array_2[0]);

byte thisOne = 1;

void setup() {
  Serial.begin(115200);

  if (thisOne == 0) {
    // pass by ARRAY type
    passByArrayType(array_1, length_1);
    passByArrayType(array_2, length_2);
  } else {
    // pass by POINTER type
    int pointer_1 = &array_1; // can not pass &array_1 "warning: invalid conversion from 'int' to 'int*'"
    int pointer_2 = &array_2; // can not pass &array_2 "warning: invalid conversion from 'int' to 'int*'"
    passByPointerType(pointer_1, length_1); // pass the address of array_1 // warning: invalid conversion from 'int' to 'int*' [-fpermissive]
    passByPointerType(pointer_2, length_2); // pass the address of array_2 // warning: invalid conversion from 'int' to 'int*' [-fpermissive]
  }
}

void loop() {
}

void passByArrayType(int myArray[], int length) {
  for (byte i = 0; i < length; i++) {
    Serial.print(myArray[i]);
  }
  Serial.println();
}

void passByPointerType(int* myPointer, int length) { // note: initializing argument 1 of 'void passByPointerType(int*, int)'
  for (byte i = 0; i < length; i++) {
    Serial.print(*(myPointer + i));
  }
  Serial.println();
}

Here is my re-gression away from multi-dimension, back to single dimension, using mathery... and it's easy coding now.

1 Like

There are a few key points you need to remember if you want to deal with arrays of arrays.

  • An array holds only one type of data.
  • cells of the arrays are contiguous in memory, starting with the one at index 0.
  • a 2D array is a 1D array of 1D arrays of the underlying type.
  • When passed "naked" to a function, an arrays decays to the pointer to the first element in the array

So, in C++, when you write something like:

char matrix[3][4];

You're declaring a 2D array and you can see that as three rows and four columns, and here's what that really means in C++:

  • matrix is an array of 3 elements.
  • Each of those 3 elements is itself an array of 4 chars.
  • So the type of matrix is char[3][4]
  • The type of matrix[0] is char[4]
  • The type of matrix[0][0] is char

And the memory layout is where people sometimes get confused:

  • The data is still stored contiguously in memory, like a flat array of 12 chars.
  • But syntactically and semantically, C++ still treats it as an array of arrays.
  • That affects how pointers work: if you write char (*p)[4] = matrix;, p is a pointer to an array of 4 char (i.e., one row). You can then do p + 1 to go to the next row.

So it’s not just a flat 1D array of 12 chars — it really is structured as arrays of arrays.

In theory you should write

char matrix1[3][4] = {
  {'A', 'B', 'C', 'D'},
  {'E', 'F', 'G', 'H'},
  {'I', 'J', 'K', 'L'}
};

This clearly shows that matrix[0] which type is char[4] has to be initialized with an array of 4 chars, and this is what you pass {'A', 'B', 'C', 'D'}

Where it gets somewhat confusing is that the compiler knows how to arrange data in memory sequentially when passed an initial value. So if you write

char matrix2[3][4] = {'A','B','C','D','E','F','G','H','I','J','K','L'};

the compiler will be fine and it's the same array. You're just providing a flat list of 12 characters; the compiler fills the array row by row (known as "row-major order"), so it works the same way: the initialiser list is simply unrolled across the array dimensions in the order they appear.

As long as the number of elements matches 3 × 4 = 12, both forms are correct.

do you want to be able to change the strings?

Then you have to change the initialization part. Unfortunately you can not use pointers to char there (or arrays of chars).

  char helloString[11] = "hello";
  char goodbyeString[11] = "goodbye";
  char nnString[11] = "nightnight";
 
  char myArray[4][2][11] = { {"first","hello" },        // can't use helloString in an initializer
                             {"second","goodbye" },     // can't use godbyString in an initializer
                             {"third","hi" },
                             {"forth","nightnight" }    // can't use nnString in an initializer
                        };
  
  Serial.printf("output\n");
  Serial.printf("1 %s\n", myArray[0][1]);
  Serial.printf("2 %s\n", myArray[1][1]);
  Serial.printf("3 %s\n", myArray[2][1]);
  Serial.printf("4 %s\n", myArray[3][1]);

Something similar can be achieved using #define.

void setup() {
  Serial.begin(115200);
  delay(250);
#define helloString "hello"
#define goodbyeString "goodbye"
#define nnString "nightnight"


  char myArray[4][2][11] = { {"first", helloString },
    {"second", goodbyeString },
    {"third", "hi" },
    {"forth", nnString }
  };

  Serial.printf("output\n");
  Serial.printf("1 %s\n", myArray[0][1]);
  Serial.printf("2 %s\n", myArray[1][1]);
  Serial.printf("3 %s\n", myArray[2][1]);
  Serial.printf("4 %s\n", myArray[3][1]);

}

void loop() {

}

Do you want to change it with pre-recorded data (ie you know about the words upfront and they will be part of the binary of your program) or does the text come from a user entry at run time (and in which case does it need to be saved permanently so that it’s still there after a reboot) ?

Further information about const

When you define

const char * goodMorning = "good morning";
const char * goodAfternoon = "good afternoon";

What you are saying is that both are pointers to constant text. The text can’t change (we say the strings are immutable, you cannot modify the characters through these pointers) but the pointer themselves are not constant, so could point elsewhere. So nothing prevents you from defining another pointer to constant char like

const char * greetingStr = goodMorning;

And later in the code decide it’s no longer the morning and time to say good afternoon and do

greetingStr = goodAfternoon;

If you wanted to have a constant pointer to constant text you would write const twice like this

const char * const goodMorning = "good morning";
const char * const goodAfternoon = "good afternoon";

So to your needs, if the key words are known upfront you can have an array of pointers pointing to constant text and just have those pointers change where they a point at (since they would not be const themselves). No need to duplicate the text in RAM.

Makes sense ?

I still don't understand why OP is messing around with a 3-D array rather than a 1-D array of struct.

Agreed

Structs still wouldn't solve the problem with initialization.

As far as I can tell, there is no "problem" with initialization. There's things you can do and things you can't and that's more or less independent of the data structure. However, to me, going with struct instead of a 3-D array is much cleaner and better organized.

What problem ?

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.