Misidentified variables

Greetings!

I have questions about IDE 2.3.2. I just ran into a weird bug and noticed a few things.

I keep my global variables in a separate file to make them easier to find. When I move the mouse over a variable, let's say a string, for example the string storing the SSID, it says type int and string. That's how I give it value and use it that way. It is natural?

image

Also, if I want to jump to a definition that can never be executed outside of the program I wrote, it would often be useful to be able to see the parameters or subfunctions to be passed.

OFF
This thing came about by using a function to process the asyncwebserver variables, as it can be found in the library description. For some reason I still don't understand, it can handle some of my global variables and some of it can't. I do not understand why.
/OFF

and what happens if you change your var name from ssid to myssid??
i'm thinking you are just conflicting with some other var already declared using the same var name..

good luck.. ~q

Hello!

I tried, but it didn't help, but as I wrote, it displays ALL my String variables like this. Here are some examples:

As I turn off all of the IDE editor enhancements..
Can't really comment on them..
Not anything against Arduino, I turn them off on all my IDE's..
They just tend to slow me down..

Gotta wait for someone that does use them..

sorry.. ~q

Have you done a successful Verify recently?

What about truly built-in types like unsigned long instead of String?

Greetings!

You can turn off the IDE's features, since you're probably much better at programming than I am. I am a Sunday programmer. I need these features and much more. For example, when I call a library function, if necessary, it would be good to jump to the definition, which maybe shows a little how to use it. I am currently browsing the sources for it on the github page. I would expect, as in the case of many other IDEs, that the autocomplete or at least the help for the given function would come up while typing, but this rarely happens and mostly with the functions I wrote. There has never been such a case that, in the case of an external function, what should and what can be written in the parentheses would have popped up. I can program a little on many platforms, I've used many IDEs. And that's why these functions are there. And they are very good for a llama like me. I also looked at platformIO, but it's a disaster. It's even slower and more complicated, even though Visual studio is not a bad IDE. Atmel has moved in well, it is perfectly usable (it was a few years ago when I used it) but the very old delphi was also good from this point of view. Or Netbeans. The last time I programmed in assembly without such help, but at that time that language somehow suited me very well.

I tried it, but, probably because I can't use it correctly, but error after error piled up. It's already a question for me how I can pass the word "string" into long unsigned. If you have some time, could you elaborate?

I do not understand. In a programming language, String is a basic type in my opinion, at least from what I read about the C language. Is it not built into this IDE? Or the translator is missing it. Is that why it is white in the IDE and not turquoise like the other definitions? If this is the case and it converts, the INT definition may not be an error, because the IDE stores it as INT. But it's very confusing. Especially if the variable doesn't work the way I want it to.

It is not. It is a class defined in the core of each Arduino boards platform.

When you are compiling for an ESP32 board, it is this code:

In C, the fundamental type for text is a zero-terminated array of char. Create a new var2.h, and paste in

String ssid = "myssid";

unsigned long forty = 40;

const char logFile[] = "/syslog.txt";

The highlighting here in the forum is similar to the IDE:

  • the reserved words for the fundamental types are in a different color
  • String is not one of them

If you hover over forty or logFile, you should see the corresponding type. For the text, it will also tell you how many bytes are used: 12 because of the automatic zero-byte at the end. (This detail, available at compile-time, is sometimes useful.) And yes, ssid is shown as an int (probably as a placeholder), because in a plain .h file like this, the type declaration for String has not been included.

Now add to the top of the file

#include <Arduino.h>

That (eventually) includes the type declaration, so after a second or two, if you hover over ssid, the hint will say String. (It's an Arduino thing, not a C thing; and not to be confused with the C++ std::string)

Note that this #include is automatically and invisibly "injected" at the top of any .ino file, so the same String declaration there will be identified correctly.

Hi!

i don't understand why string was introduced and why it's so hard to use char variables for anything? I always have to convert them here and there because one function needs this and the other that. does "const" mean constant? but how do I define the non-constant char that I want to modify relatively easily. to string together, to cut apart. Also, why do many libraries request or return a string type if it is not a natural type?

íi need learn.

Yes, const means constant. In particular for C-strings, here's why it matters. You can have

char buffer[] = "however many characters this is";
const char message[] = "Press Enter to continue";

Note in both cases, the size of the character array is omitted. The compiler will count the characters, and add one for the terminating NUL character (with the ASCII/Unicode value of zero). You can also declare a size that is bigger, in which case the extra space at the end is either more zeroes (for global variables) or uninitialized (for local variables in a function, on the stack); but not smaller. In any case, the array size is fixed.

Most functions that take a C-string declare such an argument as const char *arg

  • takes a pointer. An array name is also a pointer
  • it doesn't need a length because a properly formed C-string has the NUL, so it will just stop when that is encountered. If the NUL is not there, then the function won't behave as expected.
  • The const is the function saying, "I promise not to alter the content of the array." The compiler will enforce this promise. This won't compile
    void truncate(const char *arg) {
      arg[0] = '\0';
    }
    

So you have constant-strings with messages and text you don't want to alter, intentionally or by accident, and string-buffers in which you can compose strings by concatenating or replacing characters. If you do all this in local variables, it's not too bad. Make sure the buffers are big enough. Memory management is automatic: when the function exits, the entire stack simply "goes away", and there are no leaks.

Of course, things are not always that simple. On embedded devices, the stack sizes are smaller. You often want the string to hang around between function calls. You don't know in advance how big the string is going to be. For these reasons, you often need dynamic memory allocation. That can be done with plain old character arrays on the heap, but it is tedious and error-prone. So with C++, you use a class

In 1992 everybody was still rolling their own string classes. Remember that std::string was originally terrible, and then it became terrible and an STL-style container- but that was very late in the process, as the Committee delayed the first Standard for two years to fit in the STL

(STL is the Standard Template Library.) Arduino came quite a bit later, but was memory-constrained, so could not use std::string on those early boards. At some point -- maybe someone can say when -- they created their own String class, which to my eye is styled after Java's, introduced in 1995. Maybe the intention was to mimic them in JavaScript -- a lot more people are familiar with that -- which appeared in 1996, and intentionally copied Java's, which has methods like startsWith. Funny enough, std::string finally added starts_with and ends_with 25 years later in C++20.

So a String will handle the memory as the string grows and shrinks, and has a familiar API, including with operators like +, which in C++ is just another function. You can easily get the character array from it -- all those contiguous characters have to be in memory somewhere -- with String::c_str, which returns a const char*. It has SSO, an overloaded initialism that means Small String Optimization in this case, which reduces heap fragmentation.

Libraries use it because it is easier and more ergonomic. On the flip side, there are potential pitfalls, but less so as memory becomes more abundant even on embedded devices. It is not a built-in type of the underlying C++ language, and never will be.

OK thank you!

I understand what you're writing, but then my question would be how I could possibly solve it, say writing a log in the memory until I left. I don't usually save per line, but per 500~1000 bytes, depending on the speed of creation. How can I do this well with a char variable? Do I even know? Maybe I delete the NULL from the end and add a line with NULL at the end? I think it also stores control characters \n \r.

You wouldn't need to delete it; instead that is the first place you overwrite. If you start with a fixed-size buffer, you also want to store an index to that NUL, which is effectively also its current length. This way, you don't have to continuously re-scan to find the NUL. Put that in a struct to keep those together

struct LogCharBuffer {
  char _buf[1000];
  size_t _len;

  LogCharBuffer() {
    clear();
  }
  void clear() {
    _len = 0;
    terminate();
  }
  void terminate() {
    _buf[_len] = '\0';
  }
};

Inside this named struct, if you define a function with the same name, it gets executed whenever you create an instance of the struct. It also has no separately declared return type; this is a constructor. You could have that call other functions that are also members of the struct, which are called methods. So in this case, constructor ==> clear ==> terminate.

Then you add a method to receive any data you get

  void log(const char *msg) {
    log(msg, strlen(msg));
  }
  void log(const char *msg, size_t len) {
    while (len) {
      if (_len + len >= sizeof(_buf)) {
        flush();
      }
      while (len && _len < sizeof(_buf) - 1) {
        _buf[_len++] = *msg++;
        --len;
      }
      terminate();
    }
  }

Actually two methods with the same name, but with different type signatures. This is function overloading. If you pass just a char * (const or not), use strlen to figure out how long the C-string is. But if you already know, you can pass that directly. Then

  • the outer loop repeats while there is len remaining. It could be initially zero, in which case nothing happens at all, or until the entire incoming message is handled
  • If the current length plus the incoming length will fill the buffer entirely or overflow, then there is no room for the new terminating NUL. In that case we'll flush it first. More on that later
  • the inner loop is basic C pointer stuff that intimidates some
    • while there is some incoming left to do
    • and there is space remaining: buffer size minus one to leave room for the terminating NUL
    • copy one byte over, updating the buffer-length and incoming pointer
    • and reduce the incoming length accordingly
  • add a new NUL after the copy so far

In the flush method, you do whatever you really want to do with the data, and then clear it. That can in theory be called directly if you know you're shutting down, or periodically, so you don't lose stuff.

  void flush() {
    commit();
    clear();
  }
  void commit() {
    //TODO replace with what you really want to do
    for (int i = 0; i < _len; ++i) {
      if (i % 72 == 0) {
        Serial.print("\n--> ");
      }
      Serial.print(_buf[i]);
    }
    Serial.println();
  }

That whatever is in a method called commit, and here is a placeholder: print the buffer with a hard wrap at 72 characters. You would save to wherever. Note that nothing so far has actually depended on the fact that the _buf is NUL-terminated. But you could here; for example, just Serial.println(_buf) to print the whole thing.

With this struct in hand, you can try it in the Serial Monitor with this sketch, although you will probably want to reduce the _buf size from 1000 to 100, and the hard-wrap from 72 to 30 to make the effects more immediate

LogCharBuffer logBuffer;

void setup() {
  Serial.begin(115200);
  delay(2000);
  Serial.println("OK\nStart typing");
}

void loop() {
  char buf[15];
  if (int len = Serial.readBytes(buf, sizeof(buf)); len > 0) {
    if (len == 1 && buf[0] == '\n') {
      Serial.println("flush");
      logBuffer.flush();
    } else {
      Serial.print("read "); Serial.println(len);
      logBuffer.log(buf, len);
    }
  }
  delay(1);
}

The loop uses a randomly-sized buffer of its own to read the serial input. It could also be bigger than the one in LogCharBuffer. Pressing Enter by itself in the Serial Monitor will print anything remaining.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.