Is the location of a global function important?

I have a strange problem about where a function is placed in a sketch.
I have one void function which is located along with all the other global functions after the void setup() and void loop() functions and is called in the void loop().
The sketch errors with "'blinkLED' was not declared in this scope". (The function is listed further down.)

The really weird thing is that when I move this single function to before the void setup() function, leaving all the other global functions after the void loop() function, then all is well and the sketch compiles.

Is the location of global functions important? I have never found that to be the case before.

/////////////////////////////////////////////////////////////////////
// blinkLED
/////////////////////////////////////////////////////////////////////
void blinkLED( byte colour, int lOn = ledOn, int lOff = ledOff) {
  // This will blink a specified LED for defined on and off periods
  if (ledStatus) {
    if (millis() > ledMillis) {
      lowerLED();
      ledMillis = millis() + lOff - lOn;
      ledStatus = !ledStatus;
    }
  } else {
    if (millis() > ledMillis) {
      raiseLED(colour);
      ledMillis = millis() + lOn;
      ledStatus = !ledStatus;
    }
  }
}

It's because this function includes defaulted parameter values. It that case it must be declared before it is used.

Your blood's worth bottling! Thanks a bunch.

If you don't want to move the whole function you can just declare a function prototype at the top, and leave the actual function where it is.

void blinkLED( byte colour, int lOn = ledOn, int lOff = ledOff);

Doesn’t the ide generate prototypes for functions that have default values for the args?

Technically, all (non-static) free functions are global.

What's important is the location of the information that tells the compiler the function's name, argument count/type, and return type. That information must appear BEFORE any attempt to call the function. It may consist the the function's actual implementation or (as noted above) a function prototype.

The Arduino IDE attempts (and typically succeeds) to automatically supply function prototypes as an aid to newbies. However, in certain cases it does fail. The solution to that is to either relocate the implementation or supply your own prototype. BTW, if you ever move on to an IDE meant for serious code development, you'll have to get used to supplying your own prototypes anyway as they don't have Arduino's auto-prototype crutch.

I don't buy the likely pedagogical reasoning behind it - my thought is that anyone who has progressed to the point of coding their own function, would be pretty much ready to digest the idea of a prototype. If not, they could be encouraged to simply place the function first, and learn to make prototypes for it as another step.

Most elementary level sketches don't have any user functions. Also the concept of "declare before use" is already baked into the use of any variables, so it shouldn't be foreign.

2 Likes

Can you post the entire sketch which fails because of the position of blinkLED().
You've clearly found another flaw in the automatic prototype generation script

EDIT:
On closer inspection it does not.
What is below applied to earlier versions.
(Unless my memory is playing tricks on me.)

1 Like

It's a known flaw.

Have you a link where this particular error case is described?

So this is a wontfix. If you want to use default arguments, you have to define the prototype yourself. #3779 is now smart enough to understand that a function with default args already has its prototype and leave it alone

1 Like
void tofo(){
  x++;
}

int x=0;
void setup() {
  // put your setup code here, to run once:

}

void loop() {
  // put your main code here, to run repeatedly:

}

Gives a compile error
vs

int x=0;
void tofo(){
  x++;
}


void setup() {
  // put your setup code here, to run once:

}

void loop() {
  // put your main code here, to run repeatedly:

}

does not.
any global vars have to be declared BEFORE a function containing it.

OK thanks. I reported another error case with profile generation under the (old) builder tool and that particular compile failure was labeled an "imperfection"

Gloves are coming off. The whole idea is a crock.

I wouldn't go that far but I guess that in retrospect the introduction of the automatic generation of function prototypes feature is generally acknowledged to have been a mistake. I agree that it would have been better to have said "declare something before use" then function prototypes become unnecessary anyway. The cases where that breaks down, say for a pair of mutually recursive functions, probably would not concern too many "newbies".
However we are stuck with it because the consequences of changing this would be worse now due to the amount of code which would suddenly break.
Important is that the error cases are well known.
Fortunately, it's effect is limited to the .ino files which allows more advanced users get around it entirely.

Wontfix/imperfection: maybe some of these incorrectly or unhandled cases could at least be recognised by the Arduino processor/builder and generate an appropriate warning.

Heh. "Never mind the decade or so when C didn't even HAVE function prototypes." (they weren't "required" till C99. According to wikipedia, they were back-ported to C from C++ (!))

I think it is easy for those who spend a lot of time hanging out here on the "Programming Questions" section of the Arduino forum to get an exaggerated sense of the opposition to such a feature due to the fact that a small number (likely less than a dozen) of active forum members express their dislike of it here at every opportunity.

So you are going to see that opinion regularly here, but if you look more closely you would notice it is coming from the same few people every time.

The converse is not true. There is no reason for the ones who think it is useful to express that opinion on every forum thread where the prototype generation system worked perfectly.

Just yesterday I was thinking it would be useful to make a collection of MCVE for each distinct class of code that is not correctly handled by the Arduino sketch preprocessor, accompanied by instructions for its workaround.

I think it is a good idea.

In this specific case, the build system knows the prototype was skipped:

So you could add some output there with something like this:

ctx.Warn(tr("%[1]s:%[2]d: warning: skipped prototype generation for function with default parameter value", proto.File, proto.Line))

To get warnings like this:

C:\Users\asdf\Documents\Arduino\Foo\Foo.ino:5: warning: skipped prototype generation for function with default parameter value

However, I think it would be better to do a more sophisticated implementation where they are printed only when the compilation fails. I don't think people need to be informed of this in the case where a prototype was not necessary:

void foo(int x = 42) {}
void setup() {
  foo();
}
void loop() {}

A similar thing was done with the output of the list of libraries used by the sketch:


Unfortunately, I suspect in other cases detecting the problem is more difficult, perhaps even more so than just fixing the deficiency in the Arduino sketch preprocessor.

Yes, it is an unfortunate facet of human nature that people are more likely to criticise things that go wrong than heap praise on the things that work perfectly.

However, my attitude to this feature has been shaped by my own experience debugging code which it has silently changed and also seeing some of the outbursts of frustration from other users who have been caught by it. By its nature, it is the more experienced users because these are the ones more likely to be using constructs which are not correctly handled and these are the ones more likely to condemn the unexpected behaviour.

I actually reported one specific case of it so I could, in the future, supply a link to it as a bug in the feature instead of attempting to explain the rationale behind it and get into the sorts of arguments we are seeing here.

More transparency would be good. You've clearly identified some potential solutions.

Here is one error case for your collection which I have not reported as an issue. Others can be found in the issues for the old builder script.

/*
 * These are cases where a user created function prototype fails to suppress
 * the creation of an automatically generated function prototype leading to 
 * a compiler error.
 * The user created function prototype must be an exact copy of that from
 * the function definition otherwise it is not recognised by the builder.
 * 
 * IDE 1.8.19
 */

enum class StateA { one, two, three } ;
StateA stateA ;

void setStateA( StateA newState ) {
  stateA = newState ;
}


enum class StateB { one, two, three } ;
StateB stateB ;

// this suppresses the auto prototype creation
// void setStateB( StateB newState ) ; 

// this fails to suppresses the auto prototype creation
//  it is a valid prototype because any varaible name is optional
void setStateB( StateB ) ;  

// this fails to suppresses the auto prototype creation
//  no whitespace after the variable name
// void setStateB( StateB newState) ;  


void setStateB( StateB newState ) {
  stateB = newState ;
}

void setup() {}

void loop() {}

I guess I should ask, do these problems carry over to included .cpp and .h files, or is it only in a .ino that the automatic generation occurs? In my projects, I do have both. I often have small utility functions in the .ino and because of the auto feature, I do not supply prototypes. But I also use the .h and .cpp include method for adding functions and classes. Those follow the standard rules.

It wouldn't bother me a bit if the functions in the .ino created a problem, it's easy to fix, just add a prototype or move the function. It wouldn't affect the overall scheme very much as they are always just trivial helper functions. But, the included classes get more complicated, if things don't work normally there it could be painful because I'm already pushing the limits of my design skill to structure it well.