Puzzling array syntax?

This example from the RTCLib library works fine and I'm adapting it for my own project. But there's one line in particular I don't understand

char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

What is the purpose of its '[12]'? Is this a two-dimensional array? And if so, why?

// Date and time functions using a DS3231 RTC connected via I2C and Wire lib
#include "RTClib.h"

RTC_DS3231 rtc;

char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

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

#ifndef ESP8266
  while (!Serial); // wait for serial port to connect. Needed for native USB
#endif

  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    Serial.flush();
    while (1) delay(10);
  }

  if (rtc.lostPower()) {
    Serial.println("RTC lost power, let's set the time!");
    // When time needs to be set on a new device, or after a power loss, the
    // following line sets the RTC to the date & time this sketch was compiled
    rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
    // This line sets the RTC with an explicit date & time, for example to set
    // January 21, 2014 at 3am you would call:
    // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
  }

  // When time needs to be re-set on a previously configured device, the
  // following line sets the RTC to the date & time this sketch was compiled
  // rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
  // This line sets the RTC with an explicit date & time, for example to set
  // January 21, 2014 at 3am you would call:
  // rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
}

void loop () {
    DateTime now = rtc.now();

    Serial.print(now.year(), DEC);
    Serial.print('/');
    Serial.print(now.month(), DEC);
    Serial.print('/');
    Serial.print(now.day(), DEC);
    Serial.print(" (");
    Serial.print(daysOfTheWeek[now.dayOfTheWeek()]);
    Serial.print(") ");
    Serial.print(now.hour(), DEC);
    Serial.print(':');
    Serial.print(now.minute(), DEC);
    Serial.print(':');
    Serial.print(now.second(), DEC);
    Serial.println();

    Serial.print(" since midnight 1/1/1970 = ");
    Serial.print(now.unixtime());
    Serial.print("s = ");
    Serial.print(now.unixtime() / 86400L);
    Serial.println("d");

    // calculate a date which is 7 days, 12 hours, 30 minutes, 6 seconds into the future
    DateTime future (now + TimeSpan(7,12,30,6));

    Serial.print(" now + 7d + 12h + 30m + 6s: ");
    Serial.print(future.year(), DEC);
    Serial.print('/');
    Serial.print(future.month(), DEC);
    Serial.print('/');
    Serial.print(future.day(), DEC);
    Serial.print(' ');
    Serial.print(future.hour(), DEC);
    Serial.print(':');
    Serial.print(future.minute(), DEC);
    Serial.print(':');
    Serial.print(future.second(), DEC);
    Serial.println();

    Serial.print("Temperature: ");
    Serial.print(rtc.getTemperature());
    Serial.println(" C");

    Serial.println();
    delay(3000);
}

Yes

It's an array of strings, but a string is just an array of characters - so, effectively, you have a 2D array of characters.

Each of the strings is forced to 12 chars length (they will be padded with NULs)

Because it is not an 1D array of strings, but 2D array of chars, as follows from arrays's type:

The 2nd dimension does not need to be 12, the longest day is Wednesday, which needs 10 for the 9 letters plus the terminating null. Not sure why so many sketches and libraries use 12, unless some original code was for a non-English language and everyone blindly copied that.

1 Like

Thanks all, appreciate the fast replies. It was the '12' that mainly stumped me.

Why did not they use

char *daysOfTheWeek[7] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

which, if I've thrown it correctly, would work for any language and always use the least amount of storage?

a7

2 Likes

or, perhaps, included CRLF?

Or was just plain wrong?

I'm sure that happens a lot ...

:roll_eyes:

And works fine in the posted sketch and my several adaptations.

Its only downside is that I have to read up (again) on how to interpret the asterisk. 'Pointers' not yet intuitive for me.

Just missing const


const char *daysOfTheWeek[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
1 Like

May be the author was Vietnamese (thứ năm (Thursday) : 11 bytes in UTF8) ?

The 2D char array should also be 'const'.

const char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

Here the char arrays are const, but the pointers too them in the array are not.

Is there a difference here in respect to the "const-ness"

Nothing can be changed in the const 2D array case. You're right, in the pointer case, the pointers should be made const too:

const char * const daysOfTheWeek[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

Extra storage for the pointer. 4bytes on a 32bit machine (40%+ overhead! Hmm. 63 to 70 bytes for the double-dimensioned array (including waste on the short names) (84 bytes as written), 85 bytes with 4byte pointers (71bytes wit 2byte pointers.))

why so many sketches and libraries use 12

Direct copy to a display, rather than a print-style function? Benefit of having things be 4-byte aligned?

Could - not should ➜ That’s a coding decision. Defining the array means you are free to swap some of the pointers for other (type matching) ones. (Say if a country decides the first day of the week is Monday you could swap things around).

The const char * is the mandatory part as string literals are of that type

On some architectures the text will be in flash and the pointers in ram. So the array of pointers is smaller.

You would have to use progmem to get the same outcome on UNO

Ah, yet another reason for using a 2D array. On an AVR, it's "pretty easy" to put a 2D array into PROGMEM, but an array of pointers to strings gets significantly more complicated and messy.

Yes, that’s what surprised me most about this 2D array. Why not use PROGMEM if memory impact was the main driver.

Have you used this 2D array before? It’s from the library you recommended to me in your post #2 here:

I guess only the authors at Adafruit would be able to explain their choice.

I used it in both forms (2D and pointer) without the ‘const’ with no apparent problem. Should I have seen any side effects? Just the memory difference?

try compiling on an ESP32 with all warnings on (in the IDE's preferences) the following code:

char * daysOfTheWeek[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};

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

  for (size_t i = 0; i < 7; i++) {
    Serial.println(daysOfTheWeek[i]);
  }
}

void loop() {}

you should get a warning that you are not following the ISO C++ standard

warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]
 char * daysOfTheWeek[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
                                                                                                       ^

moreover, try to run this on an ESP32 and on an UNO

char * daysOfTheWeek[] = {"T", "TT", "TTT", "TTTT", "TTTTT", "TTTTTTT", "TTTTTTTTTTTT"};

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

  for (size_t i = 0; i < 7; i++) Serial.println(daysOfTheWeek[i]);

  // since nothing is const, we can make modifications
  strcpy(daysOfTheWeek[3], "QQQ");
  for (size_t i = 0; i < 7; i++) Serial.println(daysOfTheWeek[i]);
}

void loop() {}

on the ESP you'll see that nothing has happened. that's because the strings literals were in flash and you can't modify that

T
TT
TTT
TTTT
TTTTT
TTTTTTT
TTTTTTTTTTTT
T
TT
TTT
TTTT
TTTTT
TTTTTTT
TTTTTTTTTTTT

on the UNO the strings literal end up in RAM, so you can do the modification but look at what you get!

T
TT
TTT
TTTT
TTTTT
TTTTTTT
TTTTTTTTTTTT

Q
QQ
QQQ
TQQQ
TTTQQQ
TTTTTTTTQQQ

you actually modified ALL the strings!! that's because the compiler is free to optimize memory and reuse similar blocks of memory that ISO C++ says are supposed to be constant even if you tricked the compiler in believing they were not.

➜ types are important. Be sure to respect the types

1 Like