Strange fact about Strings

Hi all, I found something strange using the String object here is some example code:

String mystring;

int setString(String thisString);

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

void loop(){
   setString(mystring);
   Serial.println(mystring);
   while(true)
     ;
}

int setString(String thisString){
    String temp = "This is the string that should be set!";
    thisString = temp;
    return 0;
}

And the serial output is just a null string, BTW I figured out what probably was wrong, I modified the method passing a pointer to "mystring" as argument and everything worked well...

The strange fact is that with the faulty version of the method (the one I wrote down) while the serial output was just a null character, if I printed the SAME EXACT VARIABLE mystring to a EthernetClient Object well, the string was effectively updated by the method...

Here is the question, how is it possible that Serial.println(mystring) and client.println(mystring) produce a different output?? where does client.println(mystring) printed characters are stored??

Thanks, JJ

You are assigning thisString with the address of an automatic variable (temp) that then goes out of scope and who knows what happens to it.

Automatic variables are placed on the stack so when the func returns that memory is fair game for anything.

static String temp = “This is the string that should be set!”;

would work but this looks like just a small test func anyway. Does it have a real purpose?


Rob

Here is the question, how is it possible that Serial.println(mystring) and client.println(mystring) produce a different output?? where does client.println(mystring) printed characters are stored??

    String temp = "This is the string that should be set!";
    thisString = temp;

temp is allocated on the stack. The assignment does not make a copy of the character data. It simply results in two objects pointing to the same location on the stack.

    return 0;

begins the process of unwinding the stack. More on that in a minute. There is one call to setString, that does not store the return value. So, why is setString a function that returns an int? What value is there in the return code when the function only ever returns a 0?

Once a function returns, the stack area above some pointer is available to be reused on any subsequent function call. Both Serial.print() and client.print() are function calls. So, some part of the stack is overwritten when making each call. Serial.print() may or may not call any other function to perform its duties (typically it does, calling Print::write() to actually send the data).

client.print(), on the other hand, most definitely does make other function calls.

The point I'm trying to make is that there is no guarantee when the stack will be overwritten, wiping out the data on the stack that you are pointing to.

This is why making returning a pointer to memory on the stack, directly with a return statement, or indirectly as a member of an instance, is not a good idea.

Yes, it has the purpose to update my ip to send it back to no-ip.com… It is just a updater for a “DynDNS” like site…

The real function does not always return 0, it returns a different value depending on method status…

And how do you explain that the client.println(mystring) output was actually different, I’m sure about this because I received an answer from the Server with the string set by the faulty method!!

However this is the corrected code, does someone have a clue??

String mystring;

int setString(String *thisString);

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

void loop(){
   setString(&mystring);
   Serial.println(mystring);
   while(true)
     ;
}

int setString(String *thisString){
    String temp = "This is the string that should be set!";
    *(thisString) = temp;
    return 0;
}

And this is my REAL code…:

#if defined(ARDUINO) && ARDUINO > 18
#include <SPI.h>
#endif
#include <Ethernet.h>


char* PATTERN = "Current IP Address: ";

byte mac[] = { 
  0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
String oldip = "0.0.0.0";
String newip;

char* whatismyip = "checkip.dyndns.org";
char* noipUpdater = "dynupdate.no-ip.com";
EthernetClient checkip;
EthernetClient noip;


int ttw;  //times to wait 30 sec.


void setup()
{
  // start the serial library:
  Serial.begin(9600);
  // start the Ethernet connection:

  ttw=22;

  if (Ethernet.begin(mac) == 0) {
    Serial.println("Failed to configure Ethernet using DHCP");
    delay(30000);
    setup(); //retry
  }
  Serial.print("Local IP address: ");
  for (byte thisByte = 0; thisByte < 4; thisByte++) {
    // print the value of each byte of the IP address:
    Serial.print(Ethernet.localIP()[thisByte], DEC);
    Serial.print("."); 
  }
  Serial.println();
  delay(1000);

}





void loop()
{

  if(checkip.connect(whatismyip, 80)){
    Serial.println("connected to checkIP server");
    checkip.println();
  }
 
  switch(findIp(checkip, PATTERN, &newip)){

  case 0: //Corrispondenza trovata
    {

      Serial.println("Corrispondenza trovata!");
      Serial.print("My ip is: ");
      Serial.println(newip);

      if(newip!=oldip){
        Serial.println("IP Change");
        oldip=newip;
        if(noip.connect(noipUpdater, 80)){
          Serial.println("connected to IP Updater server, sending the new IP...");


          //da rifare

          noip.print("GET /nic/update?hostname=MYHOST");
          noip.print(oldip);
          noip.println(" HTTP/1.0\nHost: dynupdate.no-ip.com\nAuthorization: Basic MYUSER:MYPWD \nUser-Agent: Arduino client");

          noip.println();

          delay(4000);
          if (noip.available()){
            char c = noip.read();
            while(c!=-1){
              Serial.print(c);
              c=noip.read();
            }
            Serial.println();
          }
        }
      }
      else Serial.println("L'IP non e' cambiato");

      ttw=22; //set wait time to 11 minutes
      break;

    }
  case 1: //corrispondenza non trovata
    {
      Serial.println("Corrispondenza non presente...");

      break;
    }
  case 2: //non connesso a checkIp
    {
      Serial.println("Non connesso al Server CheckIp");
      ttw=1; //set wait time to 30 seconds and retry
      break;
    }
  default:
    break;
  }     


  noip.stop();
  checkip.stop();
  checkip.flush();
  noip.flush();



  for(int i=0; i<ttw; i++)
    delay(30000);
}



int findIp(EthernetClient client, char* pattern, String * myip){
  delay(5000);
  *(myip)="";
  int j=0;
  if (client.connected() && client.available()){
    char c = client.read();
    while(c!=-1){
      
      if(c!=*(pattern)){
      //Serial.println(*(buffer+i));
      c=client.read();
    }
    else{while(c==*(pattern+j) && *(pattern+j)!='\0'){
                //Serial.print(*(buffer+i+j));
                c=client.read();
                j++;
              }
              if (*(pattern+j)=='\0'){  //corrispondenza trovata, memorizza ip
                  while ('0'<=c && c<='9' || c=='.'){
                       *(myip)+=c;
                       c=client.read();
                  }
                  return 0;
              }
              else { //corrispondenza non trovata, continua la ricerca, rimetti indice del pattern all'inizio
                j=0;
              }
    }
  }
  return 1;    // corrispondenza non presente
    }
    return 2; // non connesso
}

And how do you explain that the client.println(mystring) output was actually different, I’m sure about this because I received an answer from the Server with the string set by the faulty method!!

I explained that. You were not paying attention.

does someone have a clue??

More than a clue.

I'm sorry for my last replay, I was in a hurry.

However considering what you told me, it is possible that that part of the stack I was pointing to has been overwritten (maybe cleared). If this is the case I can't figure out why if I first call Serial.println() (which prints nothing and so I assume it has somehow already overwritten the string i was pointing to) and LATER (and so with the string overwritten at least once by Serial.println()) I call client.println(), this last method actually finds my string back! Wasn't that part of the stack overwritten already?

I know it is nothing useful because I understood the error and the solution, however I think it's important to clear (at least to me) some concepts...

Thanks, JJ

It is possible that only part of the stack was overwritten, and then overwritten again, putting some value back.

Exactly what happened is not obvious. You simply need to heed the sign that says "data beyond this point is subject to being mucked with at any time" (that sign being the stack pointer location).

From the original post:

int setString(String thisString){
    String temp = "This is the string that should be set!";
    thisString = temp;
    return 0;
}

Graynomad: You are assigning thisString with the address of an automatic variable (temp) that then goes out of scope and who knows what happens to it.

What he did is fine as far as it goes. However thisString is passed by value, not reference. Thus you can validly change it inside the function setString, and when setString exits, no harm is done. However the caller still has the original copy.

PaulS:    String temp = "This is the string that should be set!";    thisString = temp;

temp is allocated on the stack. The assignment does not make a copy of the character data. It simply results in two objects pointing to the same location on the stack.

The assignment calls "String::operator=". That's perfectly valid. It does in fact make two copies (the original and the copy, that is). ...

  if ( _buffer != NULL ) {
    _length = rhs._length;
    strcpy( _buffer, rhs._buffer );
  }

The problem is purely that thisString was passed by value, not reference. The simplest fix would have been:

int setString(String & thisString){
    String temp = "This is the string that should be set!";
    thisString = temp;
    return 0;
}

Now it would behave.

Well it seems like someone has even more than "more than a clue"... And both of you have so many more clues than I have eheh... Thank you both a lot!