Why does this line need 'void'?

The introductory code in my sketch, before void (setup), includes this line:

void printDetail(uint8_t type, int value);

I don't understand why it needs 'void', as that is defined by the function later in the sketch:

void printDetail(uint8_t type, int value)
{
  switch (type)
  {
    case TimeOut:
      Serial.println(F("Time Out!"));
      break;
    case WrongStack:
      Serial.println(F("Stack Wrong!"));
      break;
    case DFPlayerCardInserted:
      Serial.println(F("Card Inserted!"));
      break;
  etc

It plainly does need it, because if I remove it Verify gives this error (obscure to me):
"expected constructor. destructor, or type conversion before ';' token

I don't recall seeing this in previous sketches?

The first is a function prototype, not normally required in Arduino.

It's a forward reference to tell the compiler what to do when it encounters a call to the function before the function definition.

Arduino pre-processes your source to create prototypes, so you don't see them.

Edit: just out of curiosity, did you remove just the keyword "void", or did you remove the line completely?

2 Likes

ALL function definitions MUST include a return type. If the function does not return anything, then the return type is "void", which means "nothing".

Which in some cases might be… too late!

With the forward declaration you can use the function as usual, and then (maybe) have the actual definition of the function be below all your code.

Can make for more readable code.

a7

We can study the issue with two examples; where, return types are void and int .
1. Example-1:

void printDetail(uint8_t type, int value); //function must be declared before it is defined/used
//if not declared, the Arduino IDE compiler does it for us in the background

void setup() 
{
  Serial.begin(9600);
  printDetail(8, 475); //calling program does not expect any value from called program
}

void loop() 
{

}

void printDetail(uint8_t type, int value) //function will return no value (hence void) to calling program
{
  int x = (int)type + value; //type is casted to int to make it 16-bit; 
  // x = 0x0008 + 0x01DB = x01E3 (483 in decimal)

  Serial.println(x, DEC); //shows: 483
}

2. Example-2:

int printDetail(uint8_t type, int value); //function must be declared before it is defined/used

void setup() 
{
  Serial.begin(9600);
  int y = printDetail(8, 475); //calling program expects value from called program via y
  Serial.println(y, DEC); //shows: 483
}

void loop() 
{

}

int printDetail(uint8_t type, int value) //returns int-type data to the calling program
{
  int x = (int)type + value;  // x = 0x0008 + 0x01DB = x01E3 (483 in decimal)
  return x; //x is copied into y of the calling program
}

That does not explain why one needs to specify the return type in / with a function prototype :wink:

It it the C Language requirement that the syntax of the function prototype/forward declaration should comply with the following format:

return_type identifier(actual_argument_list);

if the called program does not return any value, then the return_type should be "void" means "nothing".

Quote from net:
In computer programming, when void is used as a function return type, it indicates that the function does not return a value .

This is a declaration. This line might exist earlier as a short list because the definition is a larger body of text.

Later in the sketch, the function definition does exist, but both need the word void to differentiate the definition or declaration of a function from a constructor as indicated by the error.

A simple web search will produce many results explaining the difference between declare, define, initialize, instantiate etc. Here is one example that also helps to explain why it might exist above, separate from the definition later.

Probably for the same reason why prototypes are required in the first place. The language specification requires prototypes. The compilers compile accordingly. They are probably able to work effectively when they can rely on the prototypes. When they encounter a function call in the code, they can build the code accordingly even if the actuall function has not yet been built up. The compiler does not at that point search for the code for the function to check for return types.

1 Like

So does that mean the error would not have occurred if I'd placed the function with the other code before void(setup)? If so, is that the recommended location for functions? I chose to put it after void(loop) because that seemed to be what I'd seen in most sketches.

I only removed "void".

But the function definition does have a return type, void. My query is about the function call (if that's the right term).

No, it's not the right term.
It's a function prototype.

In what context did you think it was function call?
It's not inside another function, is it?

Thanks, that useful article did help. But mostly, like some of the other replies here, over my head as a non-programmer. Most of my coding is in copy/paste mode anyway, so I'll settle for just using it as it stands.

We don't know how it stands, because you only showed us a snippet, and didn't tell us where it came from.

Remember, Arduino is a simplification of C++ - if you want to mix the two, you need to know how both work.

And what did you think was going to achieve?

OK, I stand corrected. I suppose my train of thought was simply:

  • its name is that of a function defined later
  • it ends with a semicolon
  • so it's 'calling' for that function to be executed.

Prototypes, constructors, destructors, classes, types, etc, - no, unfortunately not understood here, and not optimistic I ever will without a C++ course.

True, although I didn't think the full code was appropriate. And I bet regulars here will have seen many of the hundreds of posts here by users of DFRobot's MP3 Mini Player module, with its DFRobotDFPlayerMini library?

FWIW here's the original in full.

/***************************************************
  DFPlayer - A Mini MP3 Player For Arduino
  <https://www.dfrobot.com/index.php?route=product/product&product_id=1121>

 ***************************************************
  This example shows the all the function of library for DFPlayer.

  Created 2016-12-07
  By [Angelo qiao](Angelo.qiao@dfrobot.com)

  GNU Lesser General Public License.
  See <http://www.gnu.org/licenses/> for details.
  All above must be included in any redistribution
 ****************************************************/

/***********Notice and Trouble shooting***************
  1.Connection and Diagram can be found here
  <https://www.dfrobot.com/wiki/index.php/DFPlayer_Mini_SKU:DFR0299#Connection_Diagram>
  2.This code is tested on Arduino Uno, Leonardo, Mega boards.
 ****************************************************/

#include "Arduino.h"
#include "SoftwareSerial.h"
#include "DFRobotDFPlayerMini.h"

SoftwareSerial mySoftwareSerial(10, 11); // RX, TX
DFRobotDFPlayerMini myDFPlayer;
void printDetail(uint8_t type, int value);

void setup()
{
  mySoftwareSerial.begin(9600);
  Serial.begin(115200);
  Serial.println("");


  Serial.println();
  Serial.println(F("DFRobot DFPlayer Mini Demo"));
  Serial.println(F("Initializing DFPlayer ... (May take 3~5 seconds)"));

  if (!myDFPlayer.begin(mySoftwareSerial))    //Use softwareSerial to communicate with mp3.
  {
    Serial.println(F("Unable to begin:"));
    Serial.println(F("1.Please recheck the connection!"));
    Serial.println(F("2.Please insert the SD card!"));
    while (true);
  }
  Serial.println(F("DFPlayer Mini online."));

  myDFPlayer.setTimeOut(500); //Set serial communictaion time out 500ms

  //----Set volume----
  myDFPlayer.volume(10);  //Set volume value (0~30).
  myDFPlayer.volumeUp(); //Volume Up
  myDFPlayer.volumeDown(); //Volume Down

  //----Set different EQ----
  myDFPlayer.EQ(DFPLAYER_EQ_NORMAL);
  //  myDFPlayer.EQ(DFPLAYER_EQ_POP);
  //  myDFPlayer.EQ(DFPLAYER_EQ_ROCK);
  //  myDFPlayer.EQ(DFPLAYER_EQ_JAZZ);
  //  myDFPlayer.EQ(DFPLAYER_EQ_CLASSIC);
  //  myDFPlayer.EQ(DFPLAYER_EQ_BASS);

  //----Set device we use SD as default----
  //  myDFPlayer.outputDevice(DFPLAYER_DEVICE_U_DISK);
  //  myDFPlayer.outputDevice(DFPLAYER_DEVICE_SD);
  //  myDFPlayer.outputDevice(DFPLAYER_DEVICE_AUX);
  //  myDFPlayer.outputDevice(DFPLAYER_DEVICE_SLEEP);
  //  myDFPlayer.outputDevice(DFPLAYER_DEVICE_FLASH);

  //----Mp3 control----
  //  myDFPlayer.sleep();     //sleep
  //  myDFPlayer.reset();     //Reset the module
  //  myDFPlayer.enableDAC();  //Enable On-chip DAC
  //  myDFPlayer.disableDAC();  //Disable On-chip DAC
  //  myDFPlayer.outputSetting(true, 15); //output setting, enable the output and set the gain to 15

  //----Mp3 play----
  //  myDFPlayer.next();  //Play next mp3
  //  delay(1000);
  //  myDFPlayer.previous();  //Play previous mp3
  //  delay(1000);
  //  myDFPlayer.play(1);  //Play the first mp3
  //  delay(5000);
  //  myDFPlayer.loop(1);  //Loop the first mp3
  //  delay(3000);
  //  myDFPlayer.pause();  //pause the mp3
  //  delay(1000);
  //  myDFPlayer.start();  //start the mp3 from the pause
  //  delay(1000);
  //  myDFPlayer.playFolder(15, 4);  //play specific mp3 in SD:/15/004.mp3; Folder Name(1~99); File Name(1~255)
  //  delay(1000);
  //  myDFPlayer.enableLoopAll(); //loop all mp3 files.
  //  delay(1000);
  //  myDFPlayer.disableLoopAll(); //stop loop all mp3 files.
  //  delay(1000);
  //  myDFPlayer.playMp3Folder(4); //play specific mp3 in SD:/MP3/0004.mp3; File Name(0~65535)
  //  delay(1000);
  //  myDFPlayer.advertise(3); //advertise specific mp3 in SD:/ADVERT/0003.mp3; File Name(0~65535)
  //  delay(1000);
  //  myDFPlayer.stopAdvertise(); //stop advertise
  //  delay(1000);
  //  myDFPlayer.playLargeFolder(2, 999); //play specific mp3 in SD:/02/004.mp3; Folder Name(1~10); File Name(1~1000)
  //  delay(1000);
  //  myDFPlayer.loopFolder(5); //loop all mp3 files in folder SD:/05.
  //  delay(1000);
  myDFPlayer.randomAll(); //Random play all the mp3.
    delay(1000);
  //  myDFPlayer.enableLoop(); //enable loop.
  //  delay(1000);
  //  myDFPlayer.disableLoop(); //disable loop.
  //  delay(1000);


  myDFPlayer.play(1);  //Play the first mp3
  delay(6000);

  //----Read imformation----
  Serial.println(myDFPlayer.readState()); //read mp3 state
  Serial.println(myDFPlayer.readVolume()); //read current volume
  Serial.println(myDFPlayer.readEQ()); //read EQ setting
  Serial.println(myDFPlayer.readFileCounts()); //read all file counts in SD card
  Serial.println(myDFPlayer.readCurrentFileNumber()); //read current play file number
  Serial.println(myDFPlayer.readFileCountsInFolder(3)); //read fill counts in folder SD:/03

//  while (1 == 1); // Stops program from proceeding
}

void loop()
{
//    static unsigned long timer = millis();
//  
//      if (millis() - timer > 11000)
//      {
//        timer = millis();
//        myDFPlayer.next();  //Play next mp3 every 3 second.
//      }
//
//  if (myDFPlayer.available())
//  {
//    printDetail(myDFPlayer.readType(), myDFPlayer.read()); //Print the detail message from DFPlayer to handle different errors and states.
//  }


}

void printDetail(uint8_t type, int value)
{
  switch (type)
  {
    case TimeOut:
      Serial.println(F("Time Out!"));
      break;
    case WrongStack:
      Serial.println(F("Stack Wrong!"));
      break;
    case DFPlayerCardInserted:
      Serial.println(F("Card Inserted!"));
      break;
    case DFPlayerCardRemoved:
      Serial.println(F("Card Removed!"));
      break;
    case DFPlayerCardOnline:
      Serial.println(F("Card Online!"));
      break;
    case DFPlayerPlayFinished:
      Serial.print(F("Number:"));
      Serial.print(value);
      Serial.println(F(" Play Finished!"));
      break;
    case DFPlayerError:
      Serial.print(F("DFPlayerError:"));
      switch (value)
      {
        case Busy:
          Serial.println(F("Card not found"));
          break;
        case Sleeping:
          Serial.println(F("Sleeping"));
          break;
        case SerialWrongStack:
          Serial.println(F("Get Wrong Stack"));
          break;
        case CheckSumNotMatch:
          Serial.println(F("Check Sum Not Match"));
          break;
        case FileIndexOut:
          Serial.println(F("File Index Out of Bound"));
          break;
        case FileMismatch:
          Serial.println(F("Cannot Find File"));
          break;
        case Advertise:
          Serial.println(F("In Advertise"));
          break;
        default:
          break;
      }
      break;
    default:
      break;
  }
}

would you have seen a difference if you'd just done

// void printDetail(uint8_t type, int value);

?

Thanks, so a sort of 'precaution'. Yet I don't see that in most sketches. Did you see my as yet unanswered question at the start of my post #10?

If you had placed the definition (full code of the function) ahead of any use, no error.

With the Arduino preprocessor, the intent, and usually successful, is that you can place the definition anywhere you like.

Your "mistake" was to use a declaration that was not complete, an error.

If you had made a complete declaration, then you may still have an error if it turned out you lied… and the error would flag a mismatch.

I don't really like the preprocessor hiding the things "real" programmers have to do, but someone decided it would make noob life easier… not expecting, I guess, anyone to bother even learning about the real world where declaration has to come before use.

I code like the source file is soup. My function definitions end up "somewhere", I never really care too much as long as all the references resolve themselves and the thing works. I use the functionality of my text processor (or even the IDE text editor) to rapidly find the definition if it needs scrutiny.

As with so many things, a matter of personal preference. I'm just bone lazy.

I would say most of the time I have functions before setup(), and loop() and functions after setup and loop and almost always setup() is found before loop(), but don't count on it. :wink:

Maybe be less lazy then I am.

a7

My understanding, in commercial C++ compilers (and maybe the C++ standard); the compiler needs to know that there is a function when compiling the "main" code.

Now the "prototype" function before Setup in Arduino IDE is that information. And here is the answer to your question:

It prototype needs to be the same as the actual function else the compiler will not know the output is void when compiling the main code.

IMHO it is unwise to define anything in more than one place. However if you must the MUST be identical.