help with scope confusion and compile order (i think)

Ok, so I’ve been at this problem for over a day now and I can’t seem to resolve it.
First, some apologia: I am new(ish) to C/Cpp and am most decidedly trying to run before I can walk.
Blatant misconceptions, lack of understanding of fundamentals and general incompetence are therefore all reasonable explanations and no offence will be taken.

Standing on the shoulders of giants my meager skills have allowed me to modify and add to examples of a (statemachine) menu system (in Cpp with includes for .h and .cpp files) and an arduino ide “compile tabs in alphabetical order” structure for controlling external hardware. I have gotten both halves to do what I want. Now I am struggling to put them together. I began by taking a single .ino tab from the arduino-style project and adding it to the menu system project.

My troubles started trying return a char array from a function in a .ino tab to the main. Having discovered that you cannot pass char arrays back from functions (told you I was new to this), I came across this method:

char *myFunction()
{
 return "returnedText"; // this returns a pointer to the string, not the string itself
}

char *str = myFunction();
Serial.println(str); // prints "returnedText"

Which, presumably because the example was old, didn’t work until I modified it to this:

void setup()
{
  Serial.begin(115200);
  delay(500);  //allow time for serial port to open
  const char *str = myFunction();
  Serial.println(str); // prints "returnedText"
}



void loop()
{
  
}

and, in a second tab, this:

const char *myFunction()
{
 return "returnedText"; // this returns a pointer to the string, not the string itself
}

Compiles and does what it says on the tin. :slight_smile:
Thinking that I was on my way to the solution, I copied the same two pieces of code to my project.

However, now when I compile, I get this error:

LcdCDTimerMeetsShowtime02.ino:413:19: error:   crosses initialization of 'const char* str'
       const char *str = myFunction();

I opened the concatenated cpp main file from the build folder for both the example and my project and, they seem logically the same i.e. the function const char *myFunction() is defined after the call to it in the setup in both cases. I (believe that I) also tried pre-declaring the function at the top of the main with no success, though I am vehemently fuzzy on when this needs to be done and when it does not.

I have also tried (am still trying) to break the single .ino tab containing my code into a .h and a .cpp file - but that leads to (a whole slew) of other problems that are beyond the scope of this question, as all I am trying to do is to pass one lousy string!

But it does beg the question as to what my strategy should be for completing the integration of the rest of the .ino tabs - should I be rewriting them as .h/.cpp?

All help/advice greatly appreciated.

Current project attached.

LcdCDTimerMeetsShowtime02.zip (27 KB)

void printOperatingDisplay(byte displayIdx)
{
  const char *str;
  
  switch (displayIdx)
  {
    case 0 :
...
      str = myFunction();

Or, you can provide a more restrictive scope for str...

void printOperatingDisplay(byte displayIdx)
{
  switch (displayIdx)
  {
    case 0 :
      {
        ...
        const char *str = myFunction();
      }
      break;

(The break may have to go inside the braces.)

"(The break may have to go inside the braces.)"

Shouldn't have to. Case doesn't require braces so the brace enclosed section isn't really any different from having a single statement there.

Agree...

char const * myFunction( void )
{
  return "Whatever, dude.";
}

int i;

void setup( void )
{
  Serial.begin( 250000 );
}

void loop( void )
{
  ++i;
  
  if ( i > 3 )
  {
    i = 0;  
  }

  switch ( i )
  {
    case 0:
      {
        char const * str = myFunction();
        Serial.println(str);
      }
      break;
    case 1:
      break;
    case 2:
      break;
    case 3:
      break;
  }
}
Sketch uses 1528 bytes (4%) of program storage space. Maximum is 32256 bytes.
Global variables use 206 bytes (10%) of dynamic memory, leaving 1842 bytes for local variables. Maximum is 2048 bytes.

Be careful with that returning a string though. As long as it is a constant string it might work. But if you have the string in a local variable and try to return a pointer to it then bad things happen.

To return a variable string you would pass a pointer from the caller, fill it in in the function, and return nothing. Or maybe return an int with the number of characters you put into the string.

From a quick glance at @bidoowee's code they seem to have a reasonable understanding of out-of-scope buffers.

Including and using the buffer size would be beneficial.

It's kind of nice to put cases in braces even when not necessary to fix the "crosses initialization of" errors because then you can fold the cases in the Arduino IDE. I would probably put the break statement inside the braces to make them more compact when folded but I could see how some people would want to leave them outside so that you can see whether any of the cases fall through at a glance.

Thanks for all the replies. Forehead slap for the braces - though I'm not really particularly clear on the why. I will read more about scope.

Delta_G: Be careful with that returning a string though. As long as it is a constant string it might work. But if you have the string in a local variable and try to return a pointer to it then bad things happen.

To return a variable string you would pass a pointer from the caller, fill it in in the function, and return nothing. Or maybe return an int with the number of characters you put into the string.

Indeed, the constant string was just a starting point for figuring out how to get ~something~ to work (bear in mind that pointers 101 was Wednesday and today is Friday ;)

The method for passing back local variables that I was going to emulate was this:

{
  char fillMe[8];
  getState(fillMe);
}

char *getState(char *whereToPutIt)
{
  strcpy(whereToPutIt, sData->stateName);
  return(whereToPutIt);
}

Which, if I understand correctly, in pseudocode would be: send the function getState a pointer to fillMe, which fills fillMe with the value of stateName and returns a pointer to whereToPutIt which is not used?

Given that the declaration for stateName in the Struct is also char [8], would this be considered adequately safe or is there a better practice?

Doing it as you suggest (without the size, as it is already known), I presume the function would become:

void getState(char *whereToPutIt)
{
  strcpy(whereToPutIt, sData->stateName);
  return;
}

Many thanks again.

Whether or not to return the pointer is a matter of style I think. To me it seems redundant since the caller sent the pointer so it should know what pointer it sent. But it can make for prettier syntax in some cases I guess. To me the number of chars is much more interesting information if the caller doesn't already know it.

bidoowee: Given that the declaration for stateName in the Struct is also char [8], would this be considered adequately safe or is there a better practice?

In the case where you already know the length, it would be better to give that number a name and use strncpy.

//somewhere ----

const int length = 8;


// When you declare fillMe ---

char fillMe[length]


//  In your function   ------

void getState(char *whereToPutIt)
{
  strncpy(whereToPutIt, sData->stateName, length);
  return;
}

That gives you a bit of a guarantee that nothing gets written out of bounds on accident.

Delta_G: ``` //somewhere ----

const int length = 8;

// When you declare fillMe ---

char fillMe[length]

//  In your function   ------

void getState(char *whereToPutIt) {  strncpy(whereToPutIt, sData->stateName, length);  return; }

Slightly safer would be:

//somewhere ----

const int length = 8;


// When you declare fillMe ---

char fillMe[length]

void setup() {
    getState(fillMe, sizeof fillMe);
}


//  In your function   ------

void getState(char *whereToPutIt, int len)
{
  strncpy(whereToPutIt, sData->stateName, len);
  return;
}

That way if 'fillMe' is accidentally declared with a size shorter than 'length' it can't cause a buffer overflow.

@Delta_G and @johnwasser, allow me to introduce you to... strlcpy

Many thanks for the highly instructive input.

[quote author=Coding Badly date=1500056771 link=msg=3339431] @Delta_G and @johnwasser, allow me to introduce you to... strlcpy[/quote] https://en.wikibooks.org/wiki/C_Programming/C_Reference/nonstandard/strlcpy

These are not C standard library functions, but are available in the libraries on several Unix operating systems, including BSD, Mac OS X, Solaris, Android and IRIX, with notable exception of glibc on Linux, although it is available from libbsd instead.

While I always appreciate a good citation I am, however, struggling to understand the relevance of the quote.

Why would using a non-standard function be a problem?

(Its use would eliminate the bug from post #10. :wink: )