Contents of char* array getting corrupt?

Hello,

my code has a modified version of the listDir function provided with SD.h. Instead of printing the contents to the serial monitor, it writes them to a char* array. Immediately after writing the, I have it printing the contents of one of them to the serial monitor and it is correct:

At end of the "listmake" function, fileArray1 is /Octopus.c

Later, the file is accessed by a modified version of the readfile function (also from SD.h). However, it fails to read. To troubleshoot this, I decided to check the value of array again just before the read function. Now I get this:

Just before readfile, filearray1 is ⸮?⸮⸮@?⸮⸮⸮'⸮?@

that's pretty different!

Here is my full code:

// The SD card is using VSPI, the display is using HSPI


/* Includes ------------------------------------------------------------------*/
#include "DEV_Config.h"
#include "EPD.h"
#include "GUI_Paint.h"
#include "imagedata.h"
#include <stdio.h>
#include <stdlib.h>
#include "FS.h" //for SD card
#include "SD.h" //for SD card
#include "SPI.h" //for SD card
//#include "DigiFrame_Functions.h"

/* Declares -------------------------------------------------------------------*/
//Create a new image cache
UBYTE *BlackImage;
/* you have to edit the startup_stm32fxxx.s file and set a big enough heap size */
UWORD Imagesize = ((EPD_4IN2_WIDTH % 8 == 0) ? (EPD_4IN2_WIDTH / 8 ) : (EPD_4IN2_WIDTH / 8 + 1)) * EPD_4IN2_HEIGHT;

byte image[30000];
char buffer[20];
char *ptr;
int SDerror = 0;
char c;
char *fileArray[40];
char loadfile[50];


/* Entry point ----------------------------------------------------------------*/

// Initialize SD card
void initSDCard() {
  if (!SD.begin()) {
    Serial.println("Card Mount Failed");
    return;
  }
  uint8_t cardType = SD.cardType();

  if (cardType == CARD_NONE) {
    Serial.println("No SD card attached");
    return;
  }
  Serial.print("SD Card Type: ");
  if (cardType == CARD_MMC) {
    Serial.println("MMC");
  } else if (cardType == CARD_SD) {
    Serial.println("SDSC");
  } else if (cardType == CARD_SDHC) {
    Serial.println("SDHC");
  } else {
    Serial.println("UNKNOWN");
  }
  uint64_t cardSize = SD.cardSize() / (1024 * 1024);
  Serial.printf("SD Card Size: %lluMB\n", cardSize);
}

//Modified SD Card read function
void readFile(fs::FS &fs, char * path) {//was const char
  Serial.printf("Reading file: %s\n", path);

  File file = fs.open(path);
  if (!file) {
    Serial.println("Failed to open file for reading");
    SDerror = 1;
    return;
  }
  int index = 0;
  Serial.print("Read from file: ");
  while (file.available() && file.read() != '\n') {}; // IGNORE FIRST LINE OF FILE
  while (file.available()) {
    int count = file.readBytesUntil(',', buffer, 20 );
    buffer[count] = '\0'; // Add null terminator
    image[index++] = strtoul(buffer, &ptr, 0); // Convert hex constant to binary
  }
  file.close();
}

//Modified listDir. Now for making arrays of picture array file names

void listMake(fs::FS &fs, const char * dirname, uint8_t levels) {
  Serial.printf("Listing directory: %s\n", dirname);

  File root = fs.open(dirname);
  if (!root) {
    Serial.println("Failed to open directory");
    return;
  }
  if (!root.isDirectory()) {
    Serial.println("Not a directory");
    return;
  }

  String fname;
  int flen;
  String ext;
  int Ccount = 0;
  char fnamebuf[50];

  File file = root.openNextFile();
  while (file) {

    if (file.isDirectory()) {
      Serial.print("  DIR : ");
      Serial.println(file.name());
      if (levels) {
        listMake(fs, file.path(), levels - 1);
      }
    } else {
      fname = file.name();
      flen = fname.length();
      ext = fname.substring(flen - 2);
      if (ext == ".c") {
        //     Serial.print("  FILE: ");
        //     Serial.print(file.name());
        //     Serial.print("  EXTENSION: ");
        //     Serial.print(ext);
        //     Serial.print("  SIZE: ");
        //     Serial.println(file.size());
        fname = "/" + fname + '\0';
        fname.toCharArray(fnamebuf, 50);
        fileArray[Ccount] = fnamebuf;
        Ccount++;
      }

    }

    file = root.openNextFile();
  }
  Serial.print("At end of listmake function, fileArray1 is ");
  Serial.println(fileArray[1]);
}


void setup()
{
  Serial.begin(115200);
  initSDCard();

  listMake(SD, "/", 0);

  printf("EPD_4IN2_test Demo\r\n");
  DEV_Module_Init();

  printf("e-Paper Init and Clear...\r\n");
  EPD_4IN2_Init();
  EPD_4IN2_Clear();
  DEV_Delay_ms(500);

  if ((BlackImage = (UBYTE *)malloc(Imagesize)) == NULL) {
    printf("Failed to apply for black memory...\r\n");
    while (1);
  }
  printf("Paint_NewImage\r\n");
  Paint_NewImage(BlackImage, EPD_4IN2_WIDTH, EPD_4IN2_HEIGHT, 0, WHITE);
  Paint_SetScale(4); //I don&t understand this but seems to be necessary

#if 1 //Welcome Screen
  Paint_Clear(WHITE);
  Paint_DrawString_EN(32, 140, "DIGIFRAME", &Font20, WHITE, BLACK);//Font 20 each char 14 pxl wide 24char = 336
  Paint_DrawString_EN(116, 170, "(Name Subject to Change)", &Font12, WHITE, BLACK);//Font 12 each char 7 pxl wide 24char = 168
  EPD_4IN2_4GrayDisplay(BlackImage);
  DEV_Delay_ms(8000);
#endif



}

/* The main loop -------------------------------------------------------------*/
void loop()
{

#if 1 //read file with array number 1
  Serial.print("Just before readfile, filearray1 is ");
  Serial.println(fileArray[1]);
  readFile(SD, fileArray[1]);
  Paint_Clear(WHITE);
  if (SDerror == 0) {
    Paint_DrawImage(image, 16, 0, 768, 300);
  }
  else {
    Paint_DrawImage(gImage_SD_Card_Error, 16, 0, 768, 300);
    SDerror = 0;
  }
  EPD_4IN2_4GrayDisplay(BlackImage);
  DEV_Delay_ms(10000);
#endif


#if 1 //CutDown
  Paint_Clear(WHITE);
  Paint_DrawImage(gImage_cutdown, 16, 0, 768, 300);
  EPD_4IN2_4GrayDisplay(BlackImage);
  DEV_Delay_ms(10000);
#endif

}

I was considering using malloc to prevent memory problems, but didn't have much luck with that, and while googling for answers, saw a lot of comments advising against the use of malloc at all...

Incidentally, I'd also be curious as to if what I'm doing on this line or not is okay

  readFile(SD, fileArray[1]);

the readFile function is expecting a char where I'm putting fileArray[1]. I was considering doing a strcpy() to pull the contents of fileArray[1 etc.] into a normal char string.

Any and all help greatly appreciated. Thanks.

That's an array of 40 pointers to char. Remove the '*' and try again.

Hi, the intent is to have space for 40 filename strings. Seems to be working when I populate it with the while(file) loop but then when I access it later the data is messed up :confused:

Where are you initialising the pointer? Better would be to do this:

char fileArray[40][10]; // 10 strings each of 40 characters

You only reserve space for the pointers, no memory for the characters.

I have it initialized up in my declares to make it global

char *fileArray[40];

please forgive my ignorance, but if I do a predefined 2-D array in that way, how would I go about recalling the full string on a specific line, eg/line 1. Would it look anything like this?

  Serial.println(fileArray[ ][1]);
  readFile(SD, fileArray[ ][1]);

If I gather correctly you want to store upto 40 strings in an array, right?

What you have done is declared a pointer to a char array of 40 elements each. But the pointer itself is not initialised to a valid address, so its pointing to some junk address and therefore junk chars...

You could also do it this way...much more intuitive

String fileArray[40];

As others have said you have not declared anywhere to hold your filenames - I also think a 2D char array would be better.

*fileArray is an array of pointers... all it holds is a list of memory addresses.

You populate it with fnamebuf's address... so at that point when you display it you see whatever is in fnamebuf. But fnamebuff is a local variable ... is gets blown away when that routine finishes... but fileArray[1] is still pointing to that memory location... which could now be used for anything.

That is reversed, the statements is declaring 40 strings of 10 characters each.

You access a specific string by using just the first index of the array, the 2nd index is not used (same as when a single-dimension char array is access by using the name without any index).

  Serial.println(fileArray[1]);
  readFile(SD, fileArray[1]);

Thanks everyone, it's working now.

I first declare this:

char fileArray2[10][50]; // 10 filenames of up to 50 characters each

later, in the listMake function, I use strcpy to record the contents of fnamebuf into the correct position of fileArray2:

strcpy(fileArray2[Ccount],fnamebuf);

finally, the file is read into the readfile functions like so:

  readFile(SD, fileArray2[1]);

What confused me with this was that I see so many examples online of people just initiating an array of pointers (eg/ char *fileArray[50]; seemingly without any provision for the memory required to store data in fileArray[1], fileArray[33] etc. that seemed odd to me, but I wondered if the memory gets allocated on the fly as those are populated. This idea appealed to me as if I pre-allocate memory for 50 bytes for each filename, and only use 10bytes on average, it's a bit of a waste. Realistically, I'm probably going to need provision for more like 10,000 filenames, so assuming 50 character limit (or 48 plus leading / and final null character) then I'm 50kb down just for my array. Not the end of the world, just doesn't feel like very efficient use of the limited memory. (using ESP32 by the way so really not the end of the world.)

Be careful, file names are in 8.3 format, which would need a 13 char array to store (12 for the name itself, and one for the terminating null).