Theory of why this doesn't really work?

Hello :slight_smile:

I have a small question about general c++ programming:

char* test1( unsigned long a )
{
  char s[13]; //under 13 = arduino reset
  sprintf(s,"<%lu>", a);
  return s;
}

char* test2( unsigned long a )
{
  char s[39]; //under 39 = weird output
  sprintf(s,"<%lu>", a);
  return s;
}

int test3( unsigned long a, char s[] )
{
  return sprintf(s,"<%lu>", a);
}

void setup()
{
  Serial.begin( 9600 );
  delay(1000);

  Serial.print( "Hello\n" );

  Serial.print( "Test1:\"" );
  Serial.print( test1(pow(2,32)) );

  Serial.print( "\"\nTest2:\"" );
  Serial.print( test2(pow(2,32)) );

  Serial.print( "\"\nTest3:\"" );
  char str[13];
  test3( pow(2,32), str );
  Serial.print( str );

  Serial.print( "\"\nGood bye" );
}

Output:

Hello
Test1:"<?        ? !     "
Test2:"<4294967295>"
Test3:"<4294967295>"
Good bye

You can see test2 and test3 work OK, they are 13 characters long. So why in test2 I need 39 cells? Why it doesn't work with 13 cells in test1, but does work in test3 with also 13 cells?!?

Someone can explain what is going on?

Thanks!

You are returning the address of a local variable. That is invalid.

char* test1( unsigned long a )
{
  char s[13]; //under 13 = arduino reset
  sprintf(s,"<%lu>", a);
  return s;
}

Outside test1 the variable "s" is out of scope. It crashes because you are writing over the stack when you attempt to use it.

(edit) And out of lifetime. The scope and lifetime of "s" is inside that function only.

Ok, but why then test2 is working?

Also this:

char* test4(unsigned long a)
{
  char s[39];
  snprintf( s, 13, "<%lu>", a );
  return s;
}

It's working, like test2 and test3.

If you use a variable out of scope like that the results are undefined. Undefined means it may or may not "seem to work" depending on external factors. For example, allocating a large variable may keep the corruption to a part of the stack that isn't important. The thing is, test1 and test2 are wrong. Passing the variable in (as in test3) is valid.

You can make up tests like that all day and get different results. The way the compiler allocates temporary variables may affect things. It might "inline" the function in one place and not another. Once you "break the rules" and use variables when they are out of scope (eg. doing malloc/free and then continuing to use the allocated memory) then all bets are off. It may "work", for a while, it may not.

Ok Nick, I know the test3 is the way to do it, I was just experimenting :slight_smile:

But what about this:

char s[13];

char* test1(unsigned long a)
{
  sprintf(s,"<%lu>", a);
  return s;
}

This is working as well, and the variable isn't local anymore. But is it still wrong to use things like that instead of the method used in test3?

No that's fine - so long as 13 is big enough for the string plus its null terminator.

You could also pass in the address like this:

void test1(char * res, unsigned long a)
{
  sprintf(res,"<%lu>", a);
}


void loop ()
{
  char s[13];
  test1 (s, pow(2,32)) ;
  Serial.print (s) ;
}

Here s is local to loop(), but not to test1. loop() finishes using s before returning so it doesn't matter that its local to loop().

OK, thank you both :wink: