-1

Within a class I have a method that is using a basic char buffer to print some data, something like

char buffer[64];
sprintf(buffer, "%s: some text: %u, %u, %u %u", string_var, u_var1, u_var2, u_var3, u_var4);
Serial.println(buffer);

All variables are defined locally to that method within the class.

For the moment I have commented out the Serial.println(buffer), as it is part of precompiler debugging section. I know that this buffer is not being printed from this location. The buffer is still being loaded with data but is NOT being printed.

In my main code I have another function using a similar format, but the buffer has a different name.

char json_buffer[256];
Serial.println(json_buffer);

again all defined locally, NOT globally.

Now this serial print line is printing the contents of the first char buffer[64], UNLESS I create it with a null termination from the off, like:

char json_buffer[256] ={"\n"};

Why is this happening? is it a memory leak? buffer overflow? I have tried increasing the size of each buffer, for example the first buffer I have tried with 512 characters size, and the 2nd with 512 also, but no matter what I try the contents of the first buffer are being printed when I print the 2nd one.

Just looking for an explanation and a robust way of preventing this. Thanks!

Full code is at: https://github.com/practable/refrigeration/tree/dev/fw/refrigerationFirmware But is a fairly extensive program

5
  • 4
    Why would you want to print a non-initialized buffer? Jan 3 at 17:32
  • 1
    Perhaps if you post all of your code, and not just a few snippets.
    – Nick Gammon
    Jan 3 at 18:01
  • @EdgarBonet If I sprintf the content I want to be in that buffer, it also prints out the contents of the first buffer infront of the string I actually wanted to be in there, I eliminated that extra step during debugging and found that the unexpected behaviour was still present.
    – ImogenWren
    Jan 4 at 9:21
  • @NickGammon The entire code is 7 pages long, I have posted the excerpt I feel is relevent to this issue. The entire program can be found at github.com/practable/refrigeration/tree/dev/fw/… I will edit the question to contain this.
    – ImogenWren
    Jan 4 at 9:23
  • 1
    If your code is a bit too long, post instead a Minimal, Reproducible Example showing the problem. Jan 4 at 9:50

3 Answers 3

3

From your linked code this doesn't look valid to me:

sprintf(json_buffer, "%s%s", json_buffer, json_footer);  

It looks like you are trying to append json_footer to json_buffer, however the sprintf will start writing to the start of json_buffer, discarding what is already there. I suggest you use strcat instead.

You are doing that a lot, and there is a possibility of a race condition, where, in the process of writing to json_buffer in that invalid way, the sprintf gets out of control and writes much more than it should.

1

Just as a complement to Nick Gammon's answer: if you want to build the JSON string using sprintf(), you should keep track of the current position of the end of the string (the \0 that terminates it): that's where you should write the next bit, thus that's the pointer you will provide to sprintf().

Also, you may want to use snprintf() instead, just to make sure you never overflow your buffer.

Here is an example of building a string by multiple calls to snprintf(), while keeping track of the current writing position:

char *build_json() {
    static char buffer[620];
    char *end = buffer + sizeof buffer;  // end of buffer
    char *p = buffer;                    // current writing position

    p += snprintf(p, end-p, "{\n");
    p += snprintf(p, end-p, "  \"timestamp\": %d,\n", 1704391201);
    p += snprintf(p, end-p, "  \"valves\": {\n");
    p += snprintf(p, end-p, "    \"V1\": %d,\n", rand() % 2);
    p += snprintf(p, end-p, "    \"V2\": %d\n", rand() % 2);
    p += snprintf(p, end-p, "  },\n");
    p += snprintf(p, end-p, "  \"relays\": {\n");
    p += snprintf(p, end-p, "    \"foo\": %d,\n", rand() % 2);
    p += snprintf(p, end-p, "    \"bar\": %d\n", rand() % 2);
    p += snprintf(p, end-p, "  }\n");
    p += snprintf(p, end-p, "}\n");
    return buffer;
}
0

Automatic variables live on the stack for the duration of the block in which they were created - probably the whole of each of those functions. They are not automatically initialized, but merely considered to be in use for the durations of execution of each function.

So the likely answer is that buffer and json_buffer overlap, at least to some degree. In that case, since you didn't initialize 'json_buffer', whatever will be printed from it is whatever data was previously left in the memory area it now occupies. We can't see your code, to say definitively, but since this scenario can describe exactly what your are seeing, I'd say it is highly likely the right explanation.

Just looking for an explanation and a robust way of preventing this.

Easy, peasy: Don't use the contents of uninitialized variables; always initialize them before use.


For the not-yet-initiated the terms might be not that clear.

  • "To initialize" generally means to write a value into a variable by some means.
  • "To use" generally means to read from the variable.

The programming language here is C++, which roughly works like this:

  • A non-static (dynamic) variable local to a function or method is not initialized without an explicit initialization or assignment:
    /* ... */ {
        int not_initialized;
        int initialized = 23;
        int written_to; // here uninitialized
        written_to = 42; // now "initialized"
    }
    
  • A static variable without explicit initialization is default-initialized:
    static int default_initialized; // = 0; by the standard's specification
    
    This is independent of the scope of the variable. It can live inside any function or method, or outside at unit level.
  • A global variable without explicit initialization is default-initialized:
    // outside any function or method
    int default_initialized; // = 0; by the standard's specification
    

With this in mind, it makes no sense to initialize dynamic variables and to write into them (what some consider as "to use") without an intermediate usage (reading it). Some compilers can even warn about that code smell:

/* ... */ {
    int initialized = 0;
    // other code without access to "initialized"
    initialized = 2024; // second write
                        // Strange, why not initialize it directly?
                        // Or did the programmer forget something in between?
}

If your compiler optimizes well, it might remove the initialization.

2
  • I was seeing the same behaviour when I was initialising the json_buffer variable by sprintf'ing some data into it, the unexpected string from the other buffer was being printed out ahead of the new data
    – ImogenWren
    Jan 4 at 9:29
  • @ImogenWren Please edit your question and provide a minimal, complete and reproducible example of the issue. I will not wade through your big source on some external web site. You can start by removing parts that are not relevant, until just about 10 to 20 lines of code are left. Jan 4 at 17:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.