Issue converting from string to String (I think)

I'm having an odd problem when I try to convert a const char to a String. This works fine:

const char* url1 = "test1";
String url2 = url1;

Serial.println(url1);
Serial.println(url2);
Serial.println(url2.c_str());

(That prints "test1" three times, as expected).

However when I use it with a struct and a Queue (using this Queue library), it fails. I'm not entirely sure where, so posting the whole stripped down code segment:

#include <cppQueue.h>

typedef struct strRec {
  //char  url[300];
  const char* url;
  const char* misc1;
} Rec;

cppQueue  queue1(sizeof(Rec), 10, LIFO, true); 

void setup() {
  Serial.begin(115200); 

  // place a test item in the queue
  String url = "http://google.com";
  Rec test = {url.c_str(), "some details"};
  queue1.push(&test);
}

void loop() {  
  while (!queue1.isEmpty())
  {
    // pull that item from the queue
    Rec rec;
    queue1.pop(&rec);
    Serial.println("There's something in the queue!");
    Serial.println();
    Serial.print(rec.url);
    Serial.print(" ");
    Serial.print(rec.misc1);
    Serial.println();

    // None of this works, it's all blank
    String url = String(rec.url);
    Serial.println("URL as String = ");
    Serial.print(url);
    Serial.println("URL as c_string = ");
    Serial.print( url.c_str() );    
    Serial.println();
  }   

  delay(1000);

}

The conversions in section below the comment "None of this works" all fail, they all print blank to the console.

I don't imagine anyone can see what I'm doing wrong?

The flow I need to reproduce:

  • place a String into the Queue (for admittedly bad reasons, it must be a String)
  • extract that String, and convert it using c_str()

The OP understands what const means, I take it?

Constants

When you don't want others (or yourself) to override existing variable values, use the const keyword (this will declare the variable as "constant", which means unchangeable and read-only):

The Op understands that this constant

was made with 'nothing' in it?

If the thingy was not a const then putting data into the structure would be ```Rec.misc1="some goop.";`'''?

Please explain.

I think c_str() returns a pointer to a C string that is destroyed when url goes out of scope at the end of setup().

Just as a test, does your sketch work when you make url global?

It does not.

Sorry I should have mentioned, I added the "const" right before posting. Which ever so slightly improved the problem, ha. But it still fails when converting to c_str().

So, the code the OP is using is the code you did not post? Hummm. OK well I tried. Good luck.

Well of course. The code I posted is a stripped down section of a 1000 line script.

Updated code with char instead of const char (oops):

#include <cppQueue.h>

typedef struct strRec {
  //char  url[300];
  char* url;
  char* misc1;
} Rec;

cppQueue  queue1(sizeof(Rec), 10, LIFO, true); 

void setup() {
  Serial.begin(115200); 

  String url = "https://google.com";
  Rec test = {url.c_str(), "some stuff"};
  queue1.push(&test);

}

void loop() {  
  // iterate through the queue
  while (!queue1.isEmpty())
  {
    Rec rec;
    queue1.pop(&rec);
    Serial.println("There's something in the queue!");
    Serial.println();
    Serial.print(rec.url);
    Serial.print(" ");
    Serial.print(rec.misc1);

    Serial.println();
    String url = String(rec.url);
    Serial.println("URL as String = ");
    Serial.print(url);

    Serial.println("URL as c_string = ");
    Serial.print( url.c_str() );
    
    Serial.println();

  }   

  delay(1000);

}

This fails to compile with the dreaded (to me) error "invalid conversion from 'const char*' to 'char*' [-fpermissive]" on the line " Rec test = {url.c_str(), "some stuff"};"

It works for me.

#include <cppQueue.h>

typedef struct strRec {
  const char* url;
  const char* misc1;
} Rec;

cppQueue  queue1(sizeof(Rec), 10, LIFO, true);
String url = "http://google.com";

void setup() {
  Serial.begin(115200);
  
  Rec test = {url.c_str(), "some details"};
  queue1.push(&test);
} 

void loop() {
  while (!queue1.isEmpty()) {
    Rec rec;
    queue1.pop(&rec);
    Serial.println("There's something in the queue!");
    Serial.println();
    Serial.print(rec.url);
    Serial.print(" ");
    Serial.print(rec.misc1);
    Serial.println();
    
    String url = String(rec.url);
    Serial.println("URL as String = ");
    Serial.print(url);
    Serial.println("URL as c_string = ");
    Serial.print(url.c_str());    
    Serial.println();
  } 
  
  delay(1000);
} 

has as output:

There's something in the queue!

http://google.com some details
URL as String = 
http://google.comURL as c_string = 
http://google.com

Note that making this variable global is not a solution.

Aha, for me too. That's an odd one, to me at least.

Agreed.

url is a class instance that gets destroyed when it goes out of scope. The function c_str() returns a pointer to a C array inside of this class instance. It does not make a copy, which may or may not be what you want.

Well shoot, I was sure that would fix it. But making a copy still doesn't work, unless I'm copying it incorrectly:

#include <cppQueue.h>

typedef struct strRec {
  char *url;
  char *misc1;
} Rec;

cppQueue  queue1(sizeof(Rec), 10, LIFO, true);

void setup() {
  Serial.begin(115200);

  String url = "http://google.com";

  // copy the String to a cstring
  char str[url.length() + 1] = {};
  strcpy(str, url.c_str());  
  Rec test = {str, "some details"};
  queue1.push(&test);
} 

void loop() {
  while (!queue1.isEmpty()) {
    Rec rec;
    queue1.pop(&rec);
    Serial.println("There's something in the queue!");
    Serial.println();
    Serial.print(rec.url);
    Serial.print(" ");
    Serial.print(rec.misc1);
    Serial.println();
    
    String url = String(rec.url);
    Serial.print("URL as String = ");
    Serial.println(url);
    Serial.print("URL as c_string = ");
    Serial.println(url.c_str());    
    Serial.println();
  } 
  
  delay(1000);
}

This compiles but outputs:

There's something in the queue!

⸮ some details
URL as String = 
⸮URL as c_string = 
⸮

Your string is still on the stack, not on the heap.

Perhaps try something like this:

  char* str = (char*)malloc(url.length() + 1);
  strcpy(str, url.c_str());
  Rec test = {str, "some details"};

Note that after retrieving this string from the queue, you need to free() it to avoid a memory leak.

That won't compile for me, I get "invalid conversion from 'void*' to 'char*' [-fpermissive]" on the line that calls malloc().

#include <cppQueue.h>

typedef struct strRec {
  char* url;
  char* misc1;
} Rec;

cppQueue  queue1(sizeof(Rec), 10, LIFO, true);
//cppQueue  queue1(500, 10, LIFO, true);

void setup() {
  Serial.begin(115200);

  String url = "http://google.com";

  // copy the String to a cstring
  //char str[url.length() + 1] = {};
  //strcpy(str, url.c_str());  

  char* str = malloc(url.length() + 2);
  strcpy(str, url.c_str());  
  
  Rec test = {str, "some details"};
  queue1.push(&test);
} 

void loop() {
  while (!queue1.isEmpty()) {
    Serial.println("There's something in the queue!");
    Rec rec;
    queue1.pop(&rec);
    Serial.println();
    Serial.print(rec.url);
    Serial.print(" ");
    Serial.print(rec.misc1);
    Serial.println();
    
    String url = String(rec.url);
    Serial.print("URL as String = ");
    Serial.println(url);
    Serial.print("URL as c_string = ");
    Serial.println(url.c_str());    
    Serial.println();
  } 
  
  delay(1000);
}

My latest edit should fix that.

Thank you, this is now working. Is this the proper way to run free() in this case?

#include <cppQueue.h>

typedef struct strRec {
  char* url;
  char* misc1;
} Rec;

cppQueue  queue1(sizeof(Rec), 10, LIFO, true);
//cppQueue  queue1(500, 10, LIFO, true);

void setup() {
  Serial.begin(115200);

  String url = "http://google.com";

  // copy the String to a cstring
  char* str = (char*)malloc(url.length() + 1);
  strcpy(str, url.c_str());
    
  Rec test = {str, "some details"};
  queue1.push(&test);
} 

void loop() {
  while (!queue1.isEmpty()) {
    Serial.println("There's something in the queue!");
    Rec rec;
    queue1.pop(&rec);
    Serial.println();
    Serial.print(rec.url);
    Serial.print(" ");
    Serial.print(rec.misc1);
    Serial.println();
    
    String url = String(rec.url);
    Serial.print("URL as String = ");
    Serial.println(url);
    Serial.print("URL as c_string = ");
    Serial.println(url.c_str());    
    Serial.println();

    // since we did a malloc() on the url, need to free it from memory now that we're done with it
    free(rec.url);
    //free(rec.misc1);
    
  } 
  
  delay(1000);
}

Yes, that looks okay.

Anyway, I don't know what you are trying to do, but all of this is not considered to be best practice. You probably can restructure your program in such a way that these things can be avoided.

Agreed, this is pretty messy. And I don't know how it will work when there are multiple different URLs in that queue.

What I'm trying to do:

  • place a String in the queue
  • then retrieve it in another "thread" (using an ESP32 and xTaskCreatePinnedToCore())
  • then convert it using c_str() since that's what the HTTPClient library expects.

Where does this string come from?

The String is a long dynamically generated URL. Condensed version here:

  String serverPath = serverName + "db_enter.php?"; 
  serverPath += "deviceID=" + stringChipId + "&";
  serverPath += "command=" + cmd + "&";

  Rec item = {serverPath, "from db entry"};
  history_queue.push(&item);