0

I am learning about pointers and char arrays and was wondering if you could help me find the optimal solution here. I want to avoid using malloc hence I chose to pass a char array to a function by reference where the values are getting filled. In my main.c

    char saved_networks[100]; // create an array to save data
    readFile123(SPIFFS, "/wifi.txt",saved_networks);
    Serial.print("saved networks=");
    Serial.println(saved_networks);

And the function:

void readFile123(fs::FS &fs, const char *path, char* return_data)
{
   
    int n=0;
    Serial.printf("Reading file: %s\n", path);

    File file = fs.open(path);
    if (!file || file.isDirectory())
    {
        Serial.println("Failed to open file for reading");
        return;
    }
    Serial.print("Read from file: ");
    while (file.available())
    {
        char c =  file.read();
        delayMicroseconds(100);
        Serial.print(c);
        //strcat(return_data, &c); //returns meditation error
        return_data[n]=c; 
        n=n+1;
    }
    file.close();

}

In the program above, I create a char array size of 100 and pass it to the function. Inside a function, I read data inside my SPIFFS file system and then assing whatever string I found there to my char array. The code above works however I have 3 questions:

1. Why I cannot use strcat(return_data, &c);

The causes the cpu to crash and return an error:

Stack smashing protect failure!
abort() was called at PC 0x40137793 on core 1
ELF file SHA256: 0000000000000000 

2. Why I cannot declare my char array as following : char* saved_networks;. If I do that, my microcontroller will crash and return an error:

Read from file: TGuru Meditation Error: Core 1 panic'ed (StoreProhibited). Exception was unhandled

3. What is the most optimal way to solve this problem? Since I do not know what will be the maximum size of the data that I read form SPIFFS, simply declaring it size of 100 may not be enough. Is there any way to declare it dynamically ? I assume the only way to do that is by using malloc? Is that true?

  • 3
    The is c++, not c. – Emanuel P Apr 20 '21 at 14:48
  • 1
    "Why I cannot use strcat(return_data, &c);" Because `c` is a single character with no zero terminator. "Why I cannot declare my char array as following : char* saved_networks;. If I do that, my microcontroller will crash and return an error:" Because you never allocated memory for `char* saved_networks` – Nina Apr 20 '21 at 14:49
  • Edit your tags. This is C++. – user14063792468 Apr 20 '21 at 14:59
  • 1
    Arduino libraries have a `String` data type, can't you use that? – anastaciu Apr 20 '21 at 15:09
  • 1
    The usual C way would be to pass the length of the array in another parameter, and modify the code so as not to access array contents beyond the specified limit. – Ian Abbott Apr 20 '21 at 15:12
  • Also, since the caller of `readFile123` later passes the buffer as a null-terminated string to `Serial.println`, you might consider actually appending a null terminator to the buffer in `readFile123`. You only get away with it so far if `readFile123` reads less than 100 chars because `char saved_networks[100];` is implicitly initialized to all zeros. – Ian Abbott Apr 20 '21 at 15:21

2 Answers2

1
  1. strcat(return_data, &c) is a <string> function, and as such, it expects actual strings and not char arrays.
    In your example, you pass the address of a char, which can be interpreted as a char array of size 1. What is the difference?
    Well, strings are null terminated char arrays, which means their last valid element is a '\0', everything after that char is ignored by those str functions.

  2. char* saved_networks; Declares a variable that can store an address of type char, you still have to allocate space! That space's address will then be stored in saved_networks.

  3. You can try to find out how big the file is before reading it. Or you can incrementally read it. Since you're using C++ you could also use std::string

Edit: when you pass the name of an array it is already a reference, so I'd say you're passing it correctly already, just need to be careful you don't exceed the space allocated (100 chars in this case).

To make it more clear, let me show you some examples of syntatic sugar:

char saved_networks[100];
saved_networks == &saved_networks[0]; // true
saved_networks[0] == *saved_networks; // true
saved_networks[50] == *(saved_networks + 50); // true
&saved_networks[50] == saved_networks + 50; // true

The +50 depends of the array type: in this case it means 50 bytes because chars are 1 byte each.

Edit 2: "h" in reality is more similar to:

char const arr[2] = {'h', '\0'};

Which implies that " is used for strings and ' for chars! This is important because str functions are expecting strings to be null terminated, or else there will be invalid memory accesses from infinite loops.

I think that's what you were missing and now you'll be able to better understand my first point.

DMeneses
  • 139
  • 1
  • 7
  • [According to this](http://www.cplusplus.com/reference/cstring/strcat/) strcat expects char arrays which are basically strings, only their issue is that a single char is not null terminated. – Nina Apr 20 '21 at 15:12
  • 1
    Thanks for the answer. About the 3. But how do I pass it as a reference then? Because I must declare a variable prior to calling the function. So I must know the size of the file inside SPIFFS before calling the read function? – Lukas Petrikas Apr 20 '21 at 15:30
  • 1
    @Nina I explained the difference between strings and char arrays. What is the confusion? I know it sounds anti-climatic but a char array is not a string unless it is terminated by a `'\0'`. – DMeneses Apr 20 '21 at 16:02
  • I am still not fully understanding why does my ```strcat(return_data, &c)``` does not work. I have tested ```strcat(return_data, "h"); ``` and that works fine. I am not able to compile when I try to do : ```strcat(return_data, c)```. When I read from my SPIFFS one character at a time, I dont see what would be the difference between c and "h". Is there something I can change in a code to get strcat to work for my program? I just want to test – Lukas Petrikas Apr 20 '21 at 16:04
  • I've edited my answer Lukas. Feel free to ask more questions. – DMeneses Apr 20 '21 at 16:11
  • Thanks for clarifying. In that case, what if I append c to be \0 terminated and then try to append it to return _data as following : ```char c = file.read(); strcat(&c,'\0'); // put null termination for every character strcat(return_data, &c); //returns meditation error``` Now my c should be same as "h" right? – Lukas Petrikas Apr 20 '21 at 16:16
  • You can't use non strings with strcat. You'd have to do it like this: `char arr[] = {c, '\0'}`. But that isn't very elegant... You should stop trying to make your code work and instead read up some more on file reading. You're trying to talk without knowing words, which can be very frustrating. Take a break and go read file reading tutorials. This website for instance is nice: https://www.cplusplus.com/doc/tutorial/files/ – DMeneses Apr 20 '21 at 16:20
  • I'm still not sure if you're trying to program in C or C++ – DMeneses Apr 20 '21 at 16:20
  • 1
    am programming in Arduino IDE c++. Anyways, your suggested solution ```char temp_storage[2] = {c, '\0'};``` works as expected so that helped me understand for sure. I know how to read from file system I just need to understand more about char arrays and how they work.. I am gonna have to do more reading on that for sure but thanks for helping. – Lukas Petrikas Apr 20 '21 at 16:43
  • Could you accept my answer or at least upvote? That's why I'm helping :p @LukasPetrikas – DMeneses Apr 21 '21 at 12:34
0

Pass the length of the array:

size_t len = sizeof(saved_networks)/sizeof(*saved_networks);
char *saved_networks = readFile123(SPIFFS, "/wifi.txt",saved_networks, &len);

And then make sure that the length is not exceeded:

char *readFile123(fs::FS &fs, const char *path, size_t *lenp)
{
    size_t chunksize = *lenp; // You can pass in chunksize you want to use
    size_t n = 0;
    size_t chunk_len = 0;
    char *return_data = malloc(chunksize);
    return_data[0]=0;
    *lenp = 0;

    ...
    while (file.available())
    {
        char c =  file.read();
        delayMicroseconds(100);
        Serial.print(c);
        return_data[n]=c; 
        n=n+1;
        chunk_len++;
        if (chunk_len == chunksize-1)
        {
            return_data = realloc(return_data, n+chunksize);
            chunk_len = 0;
        }
    }
    return_data[n]=0;
    *lenp = n;

    return return_data;
}
Devolus
  • 20,356
  • 11
  • 56
  • 104
  • Thanks for the answer. I think you misunderstood what I meant. I have initialized my saved_networks[100] size of 100 hoping that my data inside SPIFFS will not be larger than 100. However, I cannot be sure. I can declare it as a very very large size such as saved_networks[100000] but that is not efficient. I want to know if there is a way to allocate the exact amount based on the amount of data in my SPIFFS. Is the only solution malloc? – Lukas Petrikas Apr 20 '21 at 15:33
  • Sorry I missunderstood you. If you don't know beforehand how big the array can get, then the only way would be to allocate a reasonable size, and if it is exceeded, you realloc another chunk. In this case you can not pass in the array beforehand. You can only do that it you already know how big the array will be, or you can call the function multiple times with a fixed size array as you do now, but then you have to still deal with the unknown size using `malloc`. You should still apply the above changes, because otherwise your program will crash, so I leave it as an answer for now. – Devolus Apr 20 '21 at 15:40
  • I updated my answer. – Devolus Apr 20 '21 at 15:52