What's a better way of doing this? avoiding iso c++ compiler warnings

clinker8

Well-known member
I'm using @KurtE's ILI9341_t3n library. This is how I construct a rounded button with text. Here is one of many cases on a switch(buttons) statement. I iterate through the buttons.
Code:
      case 7: { // Zero Z button
        w=60-1; h=40; x=tft.width()-w-1; y=60;
        char * buttonText[1] = {"Clear Z"};   char **mytext = buttonText;
        ii = 0;   mydatum = BC_DATUM;   linecolor = ILI9341_WHITE;
        facecolor = thisGREY;           textcolor = ILI9341_WHITE;
        displayRoundedTextBox( x, y, w, h, linecolor, facecolor, 1, textcolor, 
          mytext, ii, mydatum );
        break;
where displayRoundedTextBox is defined as:
Code:
void displayRoundedTextBox( uint16_t x, uint16_t y, uint16_t w, uint16_t h, 
  int16_t borderColor, int16_t faceColor, uint8_t fontSize, int16_t fontColor, 
  char *text1[], uint8_t ii, uint8_t mydatum)  {
  // make a rounded text box with text size fontSize, color fontColor, 
  // text = text, with face color faceColor, etc. at location (x,y) with box 
  // width w, and height h.
  uint16_t radius = 5;
  tft.drawRoundRect( x-1, y-1, w+2, h+2, radius, borderColor);
  tft.fillRoundRect( x, y, w, h, radius, faceColor);
  tft.setTextColor( fontColor );
  tft.setFont(Arial_10_Bold);
  tft.setTextDatum(mydatum);                   // bottom center location
  tft.drawString( text1[ii], x+w/2, y+h/2 );// locate string according to mydatum
}

I use this construction a lot, well, because it has worked. It does generate the following compiler warning:
Code:
/home/bruce/Arduino/ELS_IDE2/touchdisplay.ino:198:33: warning: ISO C++ forbids converting a string constant to 'char*' [-Wwrite-strings]
  198 |         char * buttonText[1] = {"Clear Z"};   char **mytext = buttonText;
      |                                 ^~~~~~~~~
Worried a bit going into the future and supporting this. Don't want an update to Arduino or gcc to decide this is illegal... What is a better way of doing this that doesn't generate the warning? This stuff confuses me, I'm a crummy programmer...
 
Off the top of my head, try making your char * const, i.e:
Code:
const char * buttonText[1] = {"Clear Z"};
 
Off the top of my head, try making your char * const, i.e:
Code:
const char * buttonText[1] = {"Clear Z"};

Afraid that doesn't help much. Now the warning is:
Code:
/home/bruce/Arduino/ELS_IDE2/touchdisplay.ino:198:69: warning: invalid conversion from 'const char**' to 'char**' [-fpermissive]
  198 |         const char * buttonText[1] = {"Clear Z"};   char **mytext = buttonText;
      |                                                                     ^~~~~~~~~~
      |                                                                     |
      |                                                                     const char**
Yes, the warning has changed, but it really doesn't make it go away.
 
Any time you pass or use a string like: "ABCD"
and pass it to somewhere as a char* if will give you this type of warning, as doing somghing like
const char*sz = "ABCD";
*sz = 'Z';

will fault the processor as you are trying to write to a area of memory that is read only.
So yes I would expect it to give you this warning on a line like:
Code:
         const char * buttonText[1] = {"Clear Z"};   char **mytext = buttonText;

Probably goes away if you do:
Code:
         const char * buttonText[1] = {"Clear Z"};   const char **mytext = buttonText;

And likewise if you do:
Code:
         char * buttonText[1] = {(char*)"Clear Z"};   char **mytext = buttonText;
 
Also, you don't need to use an array of size [1]. You can just do this.

Code:
  char * buttonText = (char*)"Clear Z";
  char **mytext = &buttonText;
 
Any time you pass or use a string like: "ABCD"
and pass it to somewhere as a char* if will give you this type of warning, as doing somghing like
const char*sz = "ABCD";
*sz = 'Z';

will fault the processor as you are trying to write to a area of memory that is read only.
So yes I would expect it to give you this warning on a line like:
Code:
         const char * buttonText[1] = {"Clear Z"};   char **mytext = buttonText;

Probably goes away if you do:
Code:
         const char * buttonText[1] = {"Clear Z"};   const char **mytext = buttonText;

And likewise if you do:
Code:
         char * buttonText[1] = {(char*)"Clear Z"};   char **mytext = buttonText;

The warning vanishes from that line, but a new warning comes when doing the displayRoundedTextBox function. Is that because I have char *text1[] in the argument list? Should it then be const char *text1[] ? I use this function a lot, so it would be nice to clean this warning source up.
 
The warning vanishes from that line, but a new warning comes when doing the displayRoundedTextBox function. Is that because I have char *text1[] in the argument list? Should it then be const char *text1[] ? I use this function a lot, so it would be nice to clean this warning source up.

Yes, it is. The compiler is warning you that you have an IMPLICIT cast of const char* to char*. You can do that two ways. Either make everything const char*, or use an EXPLICIT cast to char* as in Kurt's second suggestion, shown below, which fixes the first warning without creating the second warning.

Code:
char * buttonText[1] = {(char*)"Clear Z"};   
char **mytext = buttonText;
 
Let me answer with another question...

Should it then be const char *text1[] ?

Will the calling code ever use a string constant (just using string in double quotes rather than a variable, or "string literal" in C language jargon)?

If so, then the answer is absolutely yes, the variable and the input definition to the function should both be "const char *". Using "char *" (without "const") anywhere with strings that can can be given as string literals is going to result in compiler warnings. You need to change ALL of them to "const".
 
Let me answer with another question...



Will the calling code ever use a string constant (just using string in double quotes rather than a variable, or "string literal" in C language jargon)?

If so, then the answer is absolutely yes, the variable and the input definition to the function should both be "const char *". Using "char *" (without "const") anywhere with strings that can can be given as string literals is going to result in compiler warnings. You need to change ALL of them to "const".

Paul, as you might surmise by now, I'm a terrible programmer, with a bare grasp of C. My calling code is always using the same way of doing it, as shown in the very first post. Sometimes I need to pass a two element char array and select which one to display, depending on the index I also pass. I'm simply looking for a way to clean up most of these kinds of warnings in my code, so it's easier to spot the important stuff. I don't really mind what the changes are, but it would be helpful for me to understand them. ;) I will play around with this copy of the code and see if I can eliminate these warnings.

As a direct answer, I will always pass a variable (that has been initialized to a character string) to the function.
 
but it would be helpful for me to understand them. ;)

To understand, "const char *" and "char *" are both pointers to characters. Having "const" means you will only read the character. Normal "char *" can read or write.

A string literal is a read-only group of characters.

If you have a function like displayRoundedTextBox() that takes a "char *" input, hopefully it's easy to understand how that is warning worthy? Even if nothing inside displayRoundedTextBox() actually uses the pointer to change the characters, the warning is based only on the definition of the displayRoundedTextBox() function. When you define it with "char *" rather than "const char *", that means displayRoundedTextBox() could alter the characters. That's why the compiler gives a warning when you give the function a string constant.

Likewise inside the function, if the function input parameter is a "const char *" which means it won't write to the characters and you assign it to another variable inside the function with is "char *", the compiler warning is basically saying your input definition promised it wouldn't write to the the characters, but now you're assigning it to something that can write.

If you only metric of success is the number of warnings printed, this can feel like you're not making any progress. But in terms of understanding how things should be done, your function definition needs to promise it won't write to the characters, and everything to do inside the function needs to keep that promise. Those pesky compiler warnings what you do anything with "char *" are showing you that you're doing things that might break the function definition promise not to write to whatever characters the calling code gave to displayRoundedTextBox().
 
To understand, "const char *" and "char *" are both pointers to characters. Having "const" means you will only read the character. Normal "char *" can read or write.

A string literal is a read-only group of characters.

If you have a function like displayRoundedTextBox() that takes a "char *" input, hopefully it's easy to understand how that is warning worthy? Even if nothing inside displayRoundedTextBox() actually uses the pointer to change the characters, the warning is based only on the definition of the displayRoundedTextBox() function. When you define it with "char *" rather than "const char *", that means displayRoundedTextBox() could alter the characters. That's why the compiler gives a warning when you give the function a string constant.

Likewise inside the function, if the function input parameter is a "const char *" which means it won't write to the characters and you assign it to another variable inside the function with is "char *", the compiler warning is basically saying your input definition promised it wouldn't write to the the characters, but now you're assigning it to something that can write.

If you only metric of success is the number of warnings printed, this can feel like you're not making any progress. But in terms of understanding how things should be done, your function definition needs to promise it won't write to the characters, and everything to do inside the function needs to keep that promise. Those pesky compiler warnings what you do anything with "char *" are showing you that you're doing things that might break the function definition promise not to write to whatever characters the calling code gave to displayRoundedTextBox().

Thank you so much for your patient explanation. It helps.

All that I do with the char arrays is pass them to a function which displays the array. At no time do I change the contents of the char array. Makes sense then to have const.

As for my metrics of success, the number of warnings doesn't matter that much to me. But is good practice to reduce or eliminate them to the extent possible. It clears the field somewhat, so the errors are more prominent. Thanks again for your help, I truly appreciate it.
 
here's how I do it.

const char *kris = "cool";

displayRoundedTextBox( 10, 100, 100, 50, C_RED, C_BLUE, 4, C_WHITE, kris, 11, 12);
or
displayRoundedTextBox( 10, 100, 100, 50, C_RED, C_BLUE, 4, C_WHITE, "Cool", 11, 12);

void displayRoundedTextBox( uint16_t x, uint16_t y, uint16_t w, uint16_t h,
int16_t borderColor, int16_t faceColor, uint8_t fontSize, int16_t fontColor,
const char *text1, uint8_t ii, uint8_t mydatum) {
// make a rounded text box with text size fontSize, color fontColor,
// text = text, with face color faceColor, etc. at location (x,y) with box
// width w, and height h.
uint16_t radius = 5;
tft.drawRoundRect( x-1, y-1, w+2, h+2, radius, borderColor);
tft.fillRoundRect( x, y, w, h, radius, faceColor);
tft.setTextColor( fontColor );
tft.setFont(Arial_10_Bold);
//tft.setTextDatum(mydatum); // my lib does not have this method
//tft.drawString( text1, x+w/2, y+h/2 );// my lib does not have this method
tft.print(text1);
}
 
As for my metrics of success, the number of warnings doesn't matter that much to me. But is good practice to reduce or eliminate them to the extent possible. It clears the field somewhat, so the errors are more prominent. Thanks again for your help, I truly appreciate it.
You are on the good road of trying to fix warnings because they are potential sources of bugs in the code :) You can even use a compiler flag to report warnings as errors ("-Werror" for GCC) to enforce fixing warnings. There is also different level of warning checks that can be enabled and using "-Wall" enabled them all for the most pedantic checks. Of course sometimes warnings are caused by code that's not written by yourself and not much you can do about it.

Const correctness is another tool to try to avoid bugs. Like Paul mentioned it's a promise by the interface if the data is modified. For example if you have a const pointer to some data (effectively saying that the data shouldn't change) but you pass it to an interface taking a non-const pointer, then you get compiler error telling you that you are about to modify the data that shouldn't change. The data could for example reside in immutable memory region and trying to modify it would cause a crash, or you could logically assume that the data doesn't change when calling a function taking const pointer and breaking that assumption could cause a bug. Now, sometimes an interface are not "const correct" and take non-const pointer even when they don't modify the data. For this you could cast-away the const from a pointer using const_cast, which is better than using the old C-style cast, because you can easily search for string "const_cast" later in code where this is done to check if there are some mistakes. Going the other way of passing non-const pointer to const pointer doesn't cause errors and can be done implicitly, because it's more strict limitation of changing read-write data to read-only data, which is ok.
 
Back
Top