Macro expansion across multiple files

I have a project with 10 *.cpp, one *.h, and one *.ino files in it. The header file contains the macro:

  • #define ELEMENTCOUNT(x) (sizeof(x) / sizeof(x[0])) // Macro to get array element count,*

In one situation, I call it passing in the name of this array:

const char *contestExchanges[] = {    // Exchange Message for Contest or common info:
  "                           ",      // Spaces to create 1-based array and used for erasing messages
  "CQ CQ CQ DE W8TEE",                // General CQ. Change as needed
  "599 OH",                           // DX CW contest exchange
  "1D OH",                            // Field Day See: http://www.arrl.org/files/file/Field-Day/2016/2016%20Rules.pdf
  " A W8TEE 54 OH",                   // SSCW This message is preceeded by an incrementing QSO number:http://www.arrl.org/sweepstakes
  "SKN",                              // Straight Key Night. Send this before RST report"
  "CINCINNATI,OH",                    // QTH
  "NAME JACK JACK",
  "RIG HB 5W QRP",
  "ANT DIPOLE 25 FT",                 // NOTE: Last entry has no comma at the end. The compiler automatically allocates emough memory
  "Woodland Mounds St Pk"
};

using this syntax to call it from a function in the *.ino file:

  static int count = ELEMENTCOUNT(contestExchanges) - 1;

It works fine. However, if I move the function that uses the macro to a *.cpp file, it fails with the error message:

MyButtons.h:32: error: invalid application of 'sizeof' to incomplete type 'const char* []' #define ELEMENTCOUNT(x) (sizeof(x) / sizeof(x[0])) // Macro to get array element count * ^* C:\Users\econjack\AppData\Local\Temp\arduino_build_312005\sketch\DisplayManagement.cpp:95:22: note: in expansion of macro 'ELEMENTCOUNT' * static int count = ELEMENTCOUNT(contestExchanges) - 1;*

with the carat pointing to contestExchanges. If I move it back to the .ino file (where the array is defined) it works fine. I have the array declared in the header file (e.g., using *extern) and declared in the *.ino file.

While it is fixed...sort of...I don't understand why it's a problem when move out of the *.ino file.

Please post the array declaration from the .h file and the definition from the .ino file.

econjack: It works fine. However, if I move the function that uses the macro to a *.cpp file, it fails with the error message:

rather than using macros C++ allows the use of constexpr functions:

#include "test.h"

uint8_t pinSet[] {
  1,2,3,4,5,6,7,8,9,0,11
};

size_t arraySize = NUM_ELEMENTS(sizeof(pinSet), sizeof(pinSet[0]));

void setup() {
  Serial.begin(112500);
  Serial.println(arraySize);
}

void loop() {

}
#ifndef TEST_H
#define TEST_H

constexpr size_t NUM_ELEMENTS(size_t i, size_t k) {
  return i/k;
}

#endif

the arguments must be compile time constants...

Added benefit is that you won't have #define directive collisions.

The data declaration of the *.h file is:

_ extern const char *contestExchanges[];_

The definition of the array is given my original post.

@Bulldog: That seems to be almost the same thing. I tried it and it does not work, either. I get:

_ error: invalid application of 'sizeof' to incomplete type 'const char* []'_ * static int count = NUM_ELEMENTS(sizeof(contestExchanges), sizeof(contestExchanges[0])) - 1;*

Also, I still don't know why is works with the function in the *.ino file, but not in the *.cpp file.

econjack:
Also, I still don’t know why is works with the function in the *.ino file, but not in the *.cpp file.

cpp file, not the header?

EDIT

this works?

#include "test.h"

uint8_t pinSet[] {
  1,2,3,4,5,6,7,8,9,0,11
};

const char *contestExchanges[] = {    // Exchange Message for Contest or common info:
  "                           ",      // Spaces to create 1-based array and used for erasing messages
  "CQ CQ CQ DE W8TEE",                // General CQ. Change as needed
  "599 OH",                           // DX CW contest exchange
  "1D OH",                            // Field Day See: http://www.arrl.org/files/file/Field-Day/2016/2016%20Rules.pdf
  " A W8TEE 54 OH",                   // SSCW This message is preceeded by an incrementing QSO number:http://www.arrl.org/sweepstakes
  "SKN",                              // Straight Key Night. Send this before RST report"
  "CINCINNATI,OH",                    // QTH
  "NAME JACK JACK",
  "RIG HB 5W QRP",
  "ANT DIPOLE 25 FT",                 // NOTE: Last entry has no comma at the end. The compiler automatically allocates emough memory
  "Woodland Mounds St Pk"
};

size_t arraySize = NUM_ELEMENTS(sizeof(pinSet), sizeof(pinSet[0]));
size_t heliBob = NUM_ELEMENTS(sizeof(contestExchanges), sizeof(contestExchanges[0]));

void setup() {
  Serial.begin(112500);
  Serial.println(arraySize);
  Serial.println(heliBob); // prints 11?
}

void loop() {

}
#ifndef TEST_H
#define TEST_H

constexpr size_t NUM_ELEMENTS(size_t i, size_t k) {
  return i/k;
}


#endif

The function in question is:

/*****
  Purpose: To increase/decrease the CW message index for the messages displayed at bottom of display.

  Parameter list:
    int direction      was the UP or DN buatton pressed?
    int index          the current index into the CW message array

  Return value:
    int                The new index into the array

  CAUTION:
*****/
int ChangeSwitchIndex(int direction, int index)
{
//  static int count = ELEMENTCOUNT(contestExchanges) - 1;
  static int count = NUM_ELEMENTS(sizeof(contestExchanges), sizeof(contestExchanges[0])) - 1;
  
  if (direction) {
    index++;
    if (index > count)
      index = 1;        // First element to use
  } else {
    index--;
    if (index == 0)     // First element used to erase
      index = count;
  }
  return index;
}

and that code appears in the *.cpp file. I have your function placed in the header file, as I don't want to duplicate it in the other files. Indeed, I would expect it to draw an error if I did.

OK, while the compiler is working on your *.cpp file, this:

extern const char *contestExchanges[];

tells it that there’s an array of char * out there “somewhere”. That’s all it needs for now to create object code that accesses the array. The linker will get things hooked up later.

However, it has no way of knowing how many elements are in the array. That’s only known to the compiler while it’s working on the file were the array is defined (i.e. in the *.ino file where storage is allocated).

like this:

#include "test.h"

uint8_t pinSet[] {
  1,2,3,4,5,6,7,8,9,0,11
};

const char *contestExchanges[] = {    // Exchange Message for Contest or common info:
  "                           ",      // Spaces to create 1-based array and used for erasing messages
  "CQ CQ CQ DE W8TEE",                // General CQ. Change as needed
  "599 OH",                           // DX CW contest exchange
  "1D OH",                            // Field Day See: http://www.arrl.org/files/file/Field-Day/2016/2016%20Rules.pdf
  " A W8TEE 54 OH",                   // SSCW This message is preceeded by an incrementing QSO number:http://www.arrl.org/sweepstakes
  "SKN",                              // Straight Key Night. Send this before RST report"
  "CINCINNATI,OH",                    // QTH
  "NAME JACK JACK",
  "RIG HB 5W QRP",
  "ANT DIPOLE 25 FT",                 // NOTE: Last entry has no comma at the end. The compiler automatically allocates emough memory
  "Woodland Mounds St Pk"
};

size_t arraySize = NUM_ELEMENTS(sizeof(pinSet), sizeof(pinSet[0]));
size_t heliBob = NUM_ELEMENTS(sizeof(contestExchanges), sizeof(contestExchanges[0]));

void setup() {
  Serial.begin(112500);
  Serial.println(arraySize);
  Serial.println(heliBob); // prints 11?
}

void loop() {

}

cpp:

#include "Arduino.h"
#include "test.h"

const char *wxyz[] = {    // Exchange Message for Contest or common info:
  "                           ",      // Spaces to create 1-based array and used for erasing messages
  "CQ CQ CQ DE W8TEE",                // General CQ. Change as needed
  "599 OH",                           // DX CW contest exchange
  "1D OH",                            // Field Day See: http://www.arrl.org/files/file/Field-Day/2016/2016%20Rules.pdf
  " A W8TEE 54 OH",                   // SSCW This message is preceeded by an incrementing QSO number:http://www.arrl.org/sweepstakes
  "SKN",                              // Straight Key Night. Send this before RST report"
  "CINCINNATI,OH",                    // QTH
  "NAME JACK JACK",
  "RIG HB 5W QRP",
  "ANT DIPOLE 25 FT",                 // NOTE: Last entry has no comma at the end. The compiler automatically allocates emough memory
  "Woodland Mounds St Pk"
};
size_t something = NUM_ELEMENTS(sizeof(wxyz), sizeof(wxyz[0]));

void someFunction(int, int) {
  static size_t myVar = NUM_ELEMENTS(sizeof(wxyz), sizeof(wxyz[0])) - 1;
}

header:

#ifndef TEST_H
#define TEST_H

constexpr size_t NUM_ELEMENTS(size_t i, size_t k) {
  return i/k;
}


#endif

is that what you mean? the array is also defined in the cpp file?

No, the array is declared (using extern) in the header file, but defined in the .ino file. My understanding is that *setup() and loop() must be defined within an ino file, so that's the file where the data definition occurs and is the file that defines setup() and loop().

econjack: No, the array is declared (using extern) in the header file, but defined in the *.ino file.

And therein lies your problem. See Reply #6.

@gfvalvo: I thought the INO file was compiled first, which would determine the size of the array based on its definition in the ino file. Any other subsequent files would then know its allocation. Is that wrong?

econjack: Is that wrong?

I believe it is. I’ve always understood that files -- or compilation units (fancy Computer Science vernacular) -- are compiled as stand-alone entities. The only connection between them is the Linker. I’m thinking that’s why you need the ‘extern’ to begin with. Like I said, it tells the compiler the variable’s type but the Linker will take care of supplying its absolute address.

This is stuff I mostly remember from my C coding on Unix days with manually generated makefiles. But, beyond these basics, my knowledge of Tool Chain specifics gets kind of shaky.