I am trying to make a drop down selection with gyverportal. The issue is that I don't know how many items there are so the list cannot be hard coded. Here is an example from github:
GP.SELECT("sel", "val 1,val 2,val 3", valSelect);
//where sel is the name of the field and valselect is the currently selected item.
I am very confused with the different types of text (char, char*, const char, const char*, String, char array[10]...just to name a few) and the operations with them seem a nightmare. I think gyverportal expects a "char*" for the list.
for context: The items are loaded from EEPROM and the number is stored in Length.
what I need is:
for 0 items: "none"
for 5 items: "1,2,3,4,5,none"
for 10 items: "1,2,3,4,5,6,7,8,9,10,none"
int Length=5; //I have five items to select from
char* dropdownoptions= "";
if (Length<1){
dropdownoptions="none";
}else{
char Answ[60]="1,";
for (int i=1; i<=Length; i++){
char part[3];
String str=String(i+1);
str.toCharArray(part, 3);
strcat(Answ,part);
strcat(Answ,",");
}
strcpy(dropdownoptions,Answ);
strcat(dropdownoptions,"none");
}
Serial.println (dropdownoptions);
This code has been rewritten like 30 times and compiles, but crashes the board (esp8266) aka boot-looping
Ideally this is what I would like to do
dropdownoptions= "";
for (int i=1; i<=Length; i++){
dropdownoptions+=i;
dropdownoptions+=",";
}
dropdownoptions+="none";
2 Answers 2
As explained by chrisl in his answer, you have to declare
dropdownoptions
as an array of char
large enough to hold the whole
string, including the terminating '0円'
. If you know beforehand the
maximum value of Length
, you could size the array accordingly.
Otherwise, you may compute the required size as a function of Length
:
int str_length; // size of the string, including the final '0円'
if (Length < 10)
str_length = 2 * Length + 5;
else
str_length = 3 * Length - 4; // assume Length < 100
char dropdownoptions[str_length];
Then you have to write the contents into this array. One way to do it
would be to write each individual piece into a temporary array, then
copy it to the array dropdownoptions
by using string concatenation. My
preferred way of filling the array, however, is the "zero-copy" method
consisting of writing the pieces straight into the right place of the
final array. This requires some pointer manipulation. If you are too
uncomfortable with pointers, you may skip this answer. But if you want
to learn and get some practice, please read on.
Let p
be a pointer to the current array position where you want to
write. You can pass this pointer to sprintf()
in order to write a
piece of data into the buffer at the right place. sprintf()
returns
the number of bytes written, not counting the terminating '0円'
, and
you can then advance the pointer p
by this exact amount in order to
know where to write the next piece. In code:
char *p = dropdownoptions; // current write position
for (int i = 1; i <= Length; i++) {
p += sprintf(p, "%d,", i);
}
sprintf(p, "none");
There is one drawback to using sprintf()
though: if you get the size
of the array wrong and too short, you will end up writing past its end
and corrupting your memory. If you want to be defensive, you should keep
track of the amount of available space remaining, and use the function
snprintf()
instead. This function takes, as a second parameter,
the number of bytes available in the buffer, and it will never overflow
that buffer. I find that the easiest way of keeping track of the
remaining space is to initialize a pointer to the end of the array
(technically, "one past the end" of the array), and compute the
available space as end-p
:
char *p = dropdownoptions; // current write position
const char *end = dropdownoptions + str_length; // end of the array
for (int i = 1; i <= Length; i++) {
int count = snprintf(p, end - p, "%d,", i);
p += min(count, end - p);
}
snprintf(p, end - p, "none");
The reason of min()
being used here is that snprintf()
may return a
number larger than the available space: if the buffer is too small, this
function returns the number of characters that would had been written
(not counting the terminating '0円'
) had the buffer been large enough.
-
Thanks for the detailed introduction to pointers, I have a lot more to learn. The EEPROM has 20 items that I store so now the code works with
char dropdownoptions[60];
I do not expect to ever overflow it (there are many checks in place) so I used sprintf.Gerge– Gerge11/13/2022 21:31:09Commented Nov 13, 2022 at 21:31
The reason why your code crashes is, that you are trying to write to a non-valid pointer.
With the line
char* dropdownoptions= "";
you are declaring a pointer to a character (meaning a 2 bytes big memory address) and let it point to a string literal. All string literals are placed in the read-only section of the RAM by the compiler and cannot be changed. Thus with this line you only have a pointer (memory address), but not a buffer/memory space to write to. Later you are trying
strcpy(dropdownoptions,Answ);
which wants to write to the memory location/address, where dropdownoptions
points to. But it is in the read-only section, it cannot be written, so your code crashes. And its better, that it crashes than writing data to some random memory location, where something important might be placed.
Doing
dropdownoptions="none"
is ok, since the literal string "none"
is also placed in the read-only section. So in this case only the pointer changes, not the underlying data.
If you want to write to dropdownoptions
, you need to declare it as a character array:
char dropdownoptions[70] = "";
Make it big enough, that it can hold the biggest string, that you expect. The rest of your code should then work like it is. Though the conversion from the integer i
to a string can be done shorter:
char part[3];
itoa(i+1, part, 10);
Then part
contains the number as null-terminated string, thus you can use the strX
functions on it.
stncpy()
andstrncat()
to guard against overflowing character arrays. Google them.dropdownoptions
is two bytes long and you're copying longer strings into it. It needs to be large enough to store the longest string you're copying into it.dropdownoptions
is two bytes long, since its a pointer and not a character array. It is not meant to hold a string, so it wouldn't be a solution to just make it bigger.