Finding array size no longer works (SOLVED...sort of)

For years I've used this maco:

#define ELEMENTS(x) (sizeof(x) / sizeof(x[0]))

to determine the number of elements in an array. The compiler now says:

Invalid application of 'sizeof' to incomeplete type 'int []'

The advantage was that the macro could determine the size of any array, and did not require a specified type to be present in the macro; it was "typeless". I've tried this with a void pointer, but still no joy. What am I doing wrong or have the rules changed?

Does it really say “incomeplete” ?

Do we get to see the code?
Do we get to know which complier / IDE version?

I can't post the file because of an NDA. I've tried to write a short program to illustrate the issue, but those all work fine. I'll keep trying to duplicate the error with a short section of code. Until then, I'll have to assume it's something I'm doing in the project. I'm using the 1.8.10 version of the IDE.

@AWOL: No, I misspelled "incomplete".

Then take your existing function and start stripping unnecessary things out (making sure the error still happens) until you're comfortable posting that. I have a suspicion about what you're doing, but I can't confirm that until I see what you're doing. My tests have given a different warning message than what you posted.

econjack:
@AWOL: No, I misspelled “incomplete”.

Why transcribe, when you can cut-and-paste?

I took an earlier version of the code, which was all lumped together into one big INO file, and broke it out into CPP files, mainly along lines of functionality. That’s the version that doesn’t compile. The single INO file compiles just fine, so it is something I did when I broke up the file. I’ll try to track it down tomorrow and report what I find…when I find it. Sorry for the false alarm.

Is the array to which you are attempting to apply the macro located in another source file and did you extern it in the accompanying include file?

I have all of the globals declared as extern in a project header file, and then defined (and initialize) them in the INO file. That should be fine.

Can't offer any suggestions if you won't post code. Good luck.

Does your 'extern' include the array size?

extern int stuff[];

is a valid declaration, but the macro trick won't work on it - as the error says, the type is incomplete.

All this comes down to that C/C++ don't really have arrays at all - they're just fancy pointer syntax, and the size is mostly an afterthought.

The first rank is left empty on all array declarations in the header file. Those ranks are filled in when those same arrays are defined in the INO file. If the arrays are multidimensional, only the 2nd and higher ranks are supplied in the data declarations (i.e., in the header file using extern). There are no dynamic array definitions in the code.

These kind of guessing games are no fun. Someone with 4300+ should know that. Post an MCVE.

gfvalvo:
These kind of guessing games are no fun. Someone with 4300+ should know that. Post an MCVE.

Then you should read my second post which explains why I can’t post the code. I have not been able to reproduce the error in a short example, either.

You have to understand how your code is compiled: Each CPP file* is compiled separately, and header files are just pasted into the CPP file before compilation. After the CPP files have been compiled into object files, they are linked together into the final binary.

There is no interaction between two CPP files at compile time, so there is no way your INO file can know the array size, unless you declare it in the header file.

(*) The Arduino builder stitches all INO files into a single CPP file.

Pieter

Here is a MCVE to show the problem:

sketch.ino

int stuff[] = {1,2,3,4,5};
extern void printSize(void);

void setup()
{
  Serial.begin(115200);
  printSize();
}
void loop() {}

stuff.cpp

#include <Arduino.h>
extern int stuff[];

void printSize()
{
  Serial.println(sizeof stuff / sizeof stuff[0]);
}
sketch/stuff.cpp: In function 'void printSize()':
stuff.cpp:6:25: error: invalid application of 'sizeof' to incomplete type 'int []'
   Serial.println(sizeof stuff / sizeof stuff[0]);
                         ^~~~~
exit status 1
invalid application of 'sizeof' to incomplete type 'int []'

The .cpp file has no way of knowing how the external int array was declared in the .ino. All it gets is the address from the linker.

To work around this limitation of C++, make a global ‘size_t stuffSize’ in the .ino and reference it in the .cpp. Although it is a compile-time constant you can’t declare it ‘const’ since that, apparently, implies ‘static’ which mean it can’t be seen in other “compilation units” (source files).

int stuff[] = {1,2,3,4,5};
size_t stuffSize = sizeof stuff / sizeof stuff[0];

extern void printSize(void);

void setup()
{
  Serial.begin(115200);
  printSize();
}
void loop() {}
#include <Arduino.h>
extern int stuff[];
extern size_t stuffSize;

void printSize()
{
  Serial.println(stuffSize);
}

PieterP:
You have to understand how your code is compiled: Each CPP file* is compiled separately, and header files are just pasted into the CPP file before compilation. After the CPP files have been compiled into object files, they are linked together into the final binary.

There is no interaction between two CPP files at compile time, so there is no way your INO file can know the array size, unless you declare it in the header file.

(*) The Arduino builder stitches all INO files into a single CPP file.

Pieter

I am well aware of how compiles are handled by the GCC compiler. I owned a small software company for almost 20 years and one of our lines was programming tools. We wrote and marketed our own MSDOS C compiler back in the '80's.

In the current project, I used the standard preprocessor directive (e.g., #ifndef) in each CPP file to include the necessary declarations, function prototypes, and definitions as required in each of the source files.

@John: Doesn't that approach destroy the "typelessness" of the macro? Also, you aren't passing the array to the macro, so you're limited to the named array, which isn't very flexible.

All:

First, thanks to everyone who took the time to read/post.

I'm not sure exactly what fixed it, but I deleted and retyped the preprocessor directives at the top of each file (*.ino, *.cpp, and *.h) which were used to toggle the reading of the header file which controlled the data declarations (e.g., function prototypes and extern data declarations). All I did was retype what was there in the first place. None of the directives were misspelled, or the compiler would have caught it. I'm starting to think there may have been some non-printing character somewhere near the top of the file. I have no other explanation, but the macro is now working as it should.