2

I wrote a sketch that used the millis() function in several places. I got odd behavior because I inadvertently used the plain millis in one line, without the parens. It evaluated to zero when running.

Why didn't the compiler flag this as an undeclared variable? I suspect it's because the name was already in the symbol table, but I still would have thought it'd be caught. Is this a quirk, or expected? If expected, what is the benefit?

C/C++ is not my main language so be gentle. (-:

Edit as requested to show some code. This is an incomplete sample, but shows the usage:

const unsigned long GATE_OPEN_TIMER = 7000;
const unsigned long GATE_CLOSE_TIMER = 12000;
...
unsigned long gateStopTime = 0;
...

  if (isGateCycling) {                 // if open/close cycle is running
    if (isGatePassive) {               // if we're not applying power
      if (millis() >= gateStopTime) {  // if current delay expired
        digitalWrite(GateCloseRelay, ACTIVE);
        gateStopTime = millis() + GATE_CLOSE_TIMER;
        isGatePassive = false;

... followed by similar logic for the reverse direction. If any use of millis() is replaced by millis, the code falls over but the compiler sees nothing wrong.

  • When you say you "used" millis - can you post the code? A line like this generates a compiler error: int foo = millis; The error is invalid conversion from 'long unsigned int (*)()' to 'long unsigned int' – Nick Gammon Oct 28 '16 at 0:47
  • Not sure how to format code in a comment, but.. basically the use is future = millis() + delay followed later by if millis >= future – Jim Mack Oct 28 '16 at 0:51
  • 1
    Edit the question and put the code there, please using the code formatting markdown (four leading spaces). For help see Markdown help. You should be able to do this by selecting the code and pressing Ctrl+K to have your browser do this for you. That if statement doesn't look valid anyway. – Nick Gammon Oct 28 '16 at 0:57
  • Don't just post the line, post enough code to reproduce it (ie. that compiles without errors). For example, what type is future? – Nick Gammon Oct 28 '16 at 0:58
  • Many compilers will actually produce a warning for this. – Chris Stratton Oct 28 '16 at 1:21
2

I've made up a test case:

const unsigned long GATE_OPEN_TIMER = 7000;
const unsigned long GATE_CLOSE_TIMER = 12000;
unsigned long gateStopTime = 0;

void setup()
{
  if (millis >= gateStopTime) {  // if current delay expired
    gateStopTime = millis + GATE_CLOSE_TIMER;
  }
}
void loop()
{
}

Under IDE 1.6.9 I get these errors:

/tmp/arduino_modified_sketch_345879/sketch_oct27a.ino: In function 'void setup()':
sketch_oct27a:7: error: ISO C++ forbids comparison between pointer and integer [-fpermissive]
   if (millis >= gateStopTime) {  // if current delay expired
                 ^
/tmp/arduino_modified_sketch_345879/sketch_oct27a.ino:8:29: warning: pointer to a function used in arithmetic [-Wpointer-arith]
     gateStopTime = millis + GATE_CLOSE_TIMER;
                             ^
sketch_oct27a:8: error: invalid conversion from 'long unsigned int (*)()' to 'long unsigned int' [-fpermissive]
     gateStopTime = millis + GATE_CLOSE_TIMER;
                           ^
exit status 1
ISO C++ forbids comparison between pointer and integer [-fpermissive]

Under IDE 1.6.12 they are downgraded to warnings:

/tmp/untitled109152362.tmp/sketch_oct25b/sketch_oct25b.ino: In function 'void setup()':
/tmp/untitled109152362.tmp/sketch_oct25b/sketch_oct25b.ino:9:21: warning: ISO C++ forbids comparison between pointer and integer [-fpermissive]
       if (millis >= gateStopTime) {  // if current delay expired
                     ^
/tmp/untitled109152362.tmp/sketch_oct25b/sketch_oct25b.ino:10:33: warning: pointer to a function used in arithmetic [-Wpointer-arith]
         gateStopTime = millis + GATE_CLOSE_TIMER;
                                 ^
/tmp/untitled109152362.tmp/sketch_oct25b/sketch_oct25b.ino:10:31: warning: invalid conversion from 'long unsigned int (*)()' to 'long unsigned int' [-fpermissive]
         gateStopTime = millis + GATE_CLOSE_TIMER;
                                   ^

Sketch uses 512 bytes (1%) of program storage space. Maximum is 32,256 bytes.
Global variables use 13 bytes (0%) of dynamic memory, leaving 2,035 bytes for local variables. Maximum is 2,048 bytes.

So, the compiler isn't totally letting you get away with it. You should check the warnings. But why are the warnings only warnings?

Checking the generated compile command (using verbose compiling) I see that 1.6.12 has an extra option: -fpermissive.

From the documentation:

Downgrade some diagnostics about nonconformant code from errors to warnings. Thus, using -fpermissive allows some nonconforming code to compile.

So you have "nonconformant code" but you are getting away with it.

I don't know why the Arduino devs did that. The whole point of making things like that errors is so you fix them at compile time, rather than scratching your head about why things don't work at run time.

Clearly the symbol millis exists. But it is a function (or perhaps a pointer to a function) and thus shouldn't be just compared to a number or have a number added to it. That's what those errors were about in 1.6.9.


Just remember, in C, C++ and the ilk, to call a function you always use parentheses, even if you are passing no arguments.

  • This is shocking! Why on Earth would they add -fpermissive? Maybe the core wouldn't compile without that? – Edgar Bonet Oct 28 '16 at 10:02
  • 1
    Maybe. But you would think they have had time to fix that sort of stuff. I can't see an easy way of turning it off either. For newbies they are going to get code that compiles, but fails at runtime in obscure ways. Better to fail at compile time. – Nick Gammon Oct 28 '16 at 11:05
  • Seems that this kind of errors where reported as warnings by old versions of gcc, and the -fpermissive flag was added in order to avoid build errors on old libraries, effective since Arduino 1.6.10. This has already been reported as an issue. – Edgar Bonet Oct 28 '16 at 12:46
  • @NickGammon Thanks for the detective work. I knew I wasn't crazy <g>. I had warnings turned up all the way and I still saw nothing at all. If you'd like my sketch I can post it somewhere, but at 3K bytes stripped it's probably too big to include here...? (And fwiw I'm not trying to "get away" with something, I made a typo that I still feel should have been flagged.) One puzzle to me is still why this isn't seen as an undeclared variable, or as a failure to call with (). Is it the case that in C++ a function name without () is a useful thing, seen as a pointer? – Jim Mack Oct 28 '16 at 13:03
  • @JimMack: Exactly. A function identifier without () decays into a pointer to the function. – Edgar Bonet Oct 28 '16 at 18:46
0

In short, a function in an Arduino program that gets called without () will be considered as a plain variable. Try writing a small function in your program and call it without (). Nothing will happen.

The reason the compiler didn't flag it as an undeclared variable was because millis is a variable that is declared in a kind of pre-loaded library. Think of it as a library that is always there even if you didn't include it. That is why the compiler only saw it as a regular variable.

  • Is there really a variable named millis in the arduino core, or is it referring to the function pointer? – BrettAM Oct 27 '16 at 23:43
  • It's reffered to the function pointer but a function called without () will be seen as a previously declared variable even though it was declared as a function. – Dat Ha Oct 27 '16 at 23:44
  • 1
    It is flagged as an error when I try to reproduce it. The compiler isn't stupid. – Nick Gammon Oct 28 '16 at 1:42
  • 1
    millis is a variable that is declared in a kind of pre-loaded library - you are making this stuff up. It isn't a variable, it is a function. You might more accurately call it a symbol. Whether or not it is in a library is not relevant. – Nick Gammon Oct 28 '16 at 9:15
  • 1
    'millis', without parens, is the address of the function of that name. It is properly declared; it was merely improperly used. Without parens, millis on the right hand side of an '=' evaluates to the memory address of the millis() function. Either use of the symbol (with or without parens) is valid. What was invalid was the context of use - note that the error messages were about illegal conversions or comparisons of different data-types (function pointer to long int), not about undefined symbols (which it isn't). – JRobert Oct 28 '16 at 17:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.