Trying to learn c-string functions

I am trying to rewrite a project that works for a few hours on a MKR WiFi 1010 and then just freezes. Some googling suggested that my use of the String class was likely causing heap crashes. So I would like to learn about c-string functions and better memory management.

The following code will not compile and throws an error at line 22 that reads "invalid conversion from 'char*' to 'char' [-fpermissive]"

Line 22 is "ptrValueLocation = strchr(GetPayload, srchCmd);"

I am having much confusion understanding when to use the 'char' and 'char *variable' declarations as well as how to properly use pointers to char variables.

Here is the code:

// Sandbox for learning string functions

//  *****  BEGIN DECLARATIONS *****

char GetPayload[40] = {"Door Command 1  Door Status Open"};  // Door Command is tinyint and can be either 1 or 2.  Door Status is varchar[10] and values can be either Open or Closed.
char ptrValueLocation;
char CurrentStatus[2] = {'C'};
char srchCmd = {'S'};

//  *****  END DECLARATIONS *****


void setup() {

  Serial.begin(9600);
  delay(2000);

}   // for the setup

void loop() {

  ptrValueLocation = strchr(GetPayload, srchCmd);
  Serial.print(F("Here is the first character of the current status: "));
  strncpy(CurrentStatus, ptrValueLocation + 7, 1);
  Serial.println(CurrentStatus);
  Serial.println();

  // do nothing forevermore:
  while (true);

}  // for the void loop

I appreciate any help in understanding these issues. I have spent the better part of two days reading C documentation and forum posts and still am going in circles with tweaks and re-compiles.

Ron

At least you want a pointer, not a char:

 char *ptrValueLocation;

And these should be terminated

 char CurrentStatus[2] = {'C', '\0'};

a7

The strchr() function returns a pointer, that is why ptrValueLocation needs to be declared as a pointer like @alto777 says. When you try to stuff a pointer (2 bytes) into the space reserved for a character (1 byte) the compiler complains.

Here is a good reference for the string manipulation functions. It will show the return types for each function.

Urban legend.

With ESP3/ESP8266, use of the String class is almost mandatory for serving up web-pages. So, I have not had issues in many years with real Arduino hardware: not true in the early days of Arduino.

The proper use of Strings is somewhat akin to the C command goto which is a legitimate command but can almost always be worked-around by implementing better control logic.

It is a real shame that numerous Arduino sketchers are victimized in the forum for using Strings.

properly using String Class in Arduino at DuckDuckGo

Thanks alto777! It compiles successfully now. I still don't understand why.

Two questions about the first change: Does the * make ptrValueLocation a pointer only? Are 'char* var', 'char * var' and 'char *var' equivalent?

I thought I read that the compiler would automatically add the '\0' with the 'char CurrentStatus[2] = {'C'};' statement. Did I misunderstand that?

Ron

1 Like

Thanks groundFungus. That was very helpful! I am bookmarking that link also!

Ron

Those three are identical. Old timers will use the "type declaration mimics usage" idea from C and usually write

 char *somePointer;

because we also write

char oneChar = *somePointer;

to get one char that is being pointed to.

Here's a short program to ponder:

char thisString[] = "THIS";

char *thatString = "THAT";

char yetAnother[12] = {'Y','E','T',' ','A','N','O','T','H','E','R', '\0'};

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

  Serial.print("this string: "); Serial.println(thisString);
  Serial.print("that string: "); Serial.println(thatString);
  Serial.print("yet another string: "); Serial.println(yetAnother);
}

void loop() {
}

C has a strong relationship between pointers and arrays, coming to grips with it is key to writing and reading even relatively simple C programs.

I thought I read that the compiler would automatically add the '\0' with the 'char CurrentStatus[2] = {'C'};' statement. Did I misunderstand that?

No not exactly. In the case of specifying the dimension of the array [2], and only initialising one element, the second element is made 0, which is the same as '\0', the null character, not becuase they are character arrays and the compiler adds it, but because uninitialised global variables are set to zero. So you get lucky I suppose.

I didn't have to add the null character terminator, but it never hurts too much to be explicit, either for yourself or others who may not be C native speakers, as it were. Same as adding parentheses in mathematical expressions even if you know (are you sure?) all your operator precedence rules.

I take @mrburnette's word for it that Strings are cool on the platform where they cool, but I would always want everyone to know how to deal confidently with arrays and pointers of all kinds. Character arrays are as easy a place to start on that learning as any type. Of array. Or pointer.

HTH

a7

Dang sure does help, alto777!

You guys are awesome!!

Ron

mrburnetteFaraday, I read many posts about how with constant String manipulation that the heap could eventually grow to overrun the stack and possibly cause the intermittent freezing I see with my mkr wifi 1010.

How should I have been using Strings in my previous version of this project to avoid memory conflicts?

Ron

Correct point about the compiler filling in arrays with trailing null bytes but for global variables only. So don’t rely on that for local variables.

The compiler adds the trailing null char to string literals, when you define something with double quotes, not when you use a character (simple quote) type.

const char * aPointerToTheCString = "hello world"; // trailing null char automatic. String literals should not be changed, so declared as const

char anArrayForThecString[] = "hello world"; // trailing null char automatically added for you and array configured to match the exact size with the trailing null if no size is offered

Thanks J-M-LJackson!

That really sheds light on some more of my confusion!

Ron

This can be bad:

 char someString[5] = "some longer bunch of characters";

as it will place five characters into the array and not treminate it.

So, don't do that. Let the compiler measure things whenever it can and...

Turn up compiler warnings to "all" and "verbose", whatever, in the IDE and read and heed any warnings that are issued when you verify or upload.

a7

Follow-up questions about memory management:

In the first line of my code (char GetPayload[40] ...) should I say "char *GetPayload[40] ..." instead or does that make its contents become a pointer instead of that string?

Further, that GetPayload variable is only used inside a function where I am reading in the contents of a web page with output just like the default string I set it to in the first line. Should I move the declaration of that variable to be inside the function and rely on the system to efficiently clean up that location when the function returns?

Thanks again!

Ron

Did I do that??

Well that would create an array with they type char*, so you would have an array of 40 pointers to chars. Not what you want :wink:

Global variables have their memory out of the stack, are visible by every part of the code and their lifetime is the one of the program. If you don’t need any of this, then a local variable can be a good thing. You just allocate transient memory

Thanks J-M-LJackson! So that * is what actually declares the variable to be a pointer. That nails that bit of knowledge down for me. :slightly_smiling_face:

Ron

So is the transient memory from a function in the heap space and when the function completes it cleans up leaving holes in the heap just like the String class is purported to do?

No, that was supposed to be a general post, not a reply-to.

It was just one of several experiments I did.

I try to stick to what I know I know. On the other hand I try to learn something every day. Sometimes things about microprocessors and programming…

a7

Ok. Thx

That’s a great habit

No. That memory is on the stack and is available for a new use when the function exits.

a7