Is something crazy with the ESP32 S3 or with me?

I consider this to be strange behavior if not simply erroneous. Either something is wrong with the core programming, the ESP32 S3 chip itself, or I am completely batty (the latter having already been government certified).

This program:

typedef struct {char path[40];
                char stat[4];
                uint64_t filesize;
                uint64_t written
               } FMupload;

FMupload FMuploads[]={{"","E",0,0},{"","E",0,0},{"","E",0,0},{"","E",0,0},{"","E",0,0}};
const int numFMuploads = sizeof(FMuploads) / sizeof(FMupload);

void setup(){
  Serial.begin(115200);
  for(int i=0; i < numFMuploads; i++){
    dump(i);
  }
  Serial.println("Changing Path");
  strcpy(FMuploads[0].path, "/thisfile.txt");
  dump(0);
  Serial.println("Changing Status");
  strcpy(FMuploads[0].stat, "I");
  dump(0);
  Serial.println("Changing Filesize");
  FMuploads[0].filesize = 2048;
  dump(0);
  Serial.println("Changing Written");
  FMuploads[0].written = 1024;
  dump(0);
}

void loop(){

}

void dump(int i){
  Serial.printf("Index: %i Path: %s Status %s Filesize %d Written %d\n", i, FMuploads[i].path, FMuploads[i].stat, FMuploads[i].filesize, FMuploads[i].written);
}

Produces random output similar to this:

Index: 0 Path:  Status E Filesize 0 Written 0
Index: 1 Path:  Status E Filesize 1070513980 Written 0
Index: 2 Path:  Status E Filesize 1070513980 Written 0
Index: 3 Path:  Status E Filesize 1070513980 Written 0
Index: 4 Path:  Status E Filesize 1070513980 Written 0
Changing Path
Index: 0 Path: /thisfile.txt Status E Filesize 1070513980 Written 0
Changing Status
Index: 0 Path: /thisfile.txt Status I Filesize 1070516488 Written 0
Changing Filesize
Index: 0 Path: /thisfile.txt Status I Filesize 1070516488 Written 2048
Changing Written
Index: 0 Path: /thisfile.txt Status I Filesize 1070516488 Written 2048

Strange behavior:

  1. Although the array is created with five identical inline objects, the first has the correct filesize of 0 but the others have a big number which is apparently random and changes every time.

  2. After changing the path, the filesize was also changed to a different big number.

  3. After changing the status, the filesize has a different big number in it. The filesize changed when only the status should have.

  4. After changing the filesize, it remains unchanged, but written gets changed instead.

  5. After changing written, no change was made at all.

IDE 1.8.19
ESP32 by Espressif Systems 2.0.14

Can someone please either point out what I am doing wrong or verify that this program simply doesn't work?

Thank you!

the code does not compile (missing ;in the struct)

the printf format is not suited for the arguments, filesize is a uint64_t for example, you can't print that with %d

if you activate the warnings in the preferences you'll see that

sketch_mar14a.ino: In function 'void dump(int)':
sketch_mar14a:35:17: error: format '%d' expects argument of type 'int', but argument 6 has type 'uint64_t' {aka 'long long unsigned int'} [-Werror=format=]
   Serial.printf("Index: %i Path: %s Status %s Filesize %d Written %d\n", i, FMuploads[i].path, FMuploads[i].stat, FMuploads[i].filesize, FMuploads[i].written);
                 ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~                                           ~~~~~~~~~~~~~~~~~~~~~
sketch_mar14a:35:17: error: format '%d' expects argument of type 'int', but argument 7 has type 'uint64_t' {aka 'long long unsigned int'} [-Werror=format=]
cc1plus: some warnings being treated as errors
exit status 1
format '%d' expects argument of type 'int', but argument 6 has type 'uint64_t' {aka 'long long unsigned int'} [-Werror=format=]

1 Like

You have brackets like it's a one-dimensional array, but with your braces it's a two-dimensional array.

I'm not sure if combining strings and ints this way is proper.

no it's an array of struct. it's fine.

this would compile and work

struct FMupload {
  char path[40];
  char stat[4];
  uint64_t filesize;
  uint64_t written;
};

FMupload FMuploads[] = {{"", "E", 0, 0}, {"", "E", 0, 0}, {"", "E", 0, 0}, {"", "E", 0, 0}, {"", "E", 0, 0}};
const int numFMuploads = sizeof FMuploads  / sizeof *FMuploads;

void setup() {
  Serial.begin(115200);
  for (int i = 0; i < numFMuploads; i++) {
    dump(i);
  }
  Serial.println("Changing Path");
  strlcpy(FMuploads[0].path, "/thisfile.txt", sizeof FMuploads[0].path);
  dump(0);
  
  Serial.println("Changing Status");
  strlcpy(FMuploads[0].stat, "I", sizeof FMuploads[0].stat);
  dump(0);
  
  Serial.println("Changing Filesize");
  FMuploads[0].filesize = 2048;
  dump(0);
  
  Serial.println("Changing Written");
  FMuploads[0].written = 1024;
  dump(0);
}

void loop() {}

void dump(int i) {
  Serial.printf("Index: %i Path: %s Status %s Filesize %llu Written %llu\n", i, FMuploads[i].path, FMuploads[i].stat, FMuploads[i].filesize, FMuploads[i].written);
}

I changed the strcpy for strlcy() to limit the risk of buffer overflow

Thanks for the responses.

@runaway_pancake
The missing ; in the struct apparently came from something I did while editing the post. It's there in the program and it did compile.

I increased warnings from default to more and saw the warnings you mentioned. Thanks for pointing those out.

I actually don't need uint64_t as the maximum filesize on Fat16 will fit in an unsigned long. I made that change and the program works.

THANK YOU!!!

I will have to think about how to print long long though because millis() is of that type and the maximum device size under Fat16 also exceeds the capacity of unsigned long.

@J-M-L
As for the single/two dimension array. I think it is a single dimension array of objects (the structure), the internal sets of braces are simply the syntax for representing the object within the array. Because it is an obect/structure, combining strings and ints is fine. You are correct that that simply wouldn't work if it were truly a two dimensional array because the second dimension would have elements of differing data types.

Thanks for your feedback.

you mixed the names in your answer :slight_smile:

@runaway_pancake and @J-M-L Sorry, I switched my responses to both of you. Like I said, government certified batty. :slight_smile:

As for the strlcpy, thanks for that tip as well. I wasn't aware of that function.

I still think the printed results were batty. Showing one element of the structure as changed when another one is is weird.

Changing the data types fixed the problem and I have marked the issue as solved.

Thanks!

That's because printf uses the formats to decide where in memory the next piece of data is going to be: the function can take any number of parameters of various types, so the code for printf does try to guess how many bytes deep in the stack it needs to go to find the parameters. if the format is not correct, the function makes a wrong guess and goes reading bytes that are not part of your parameter.

➜ So it's super important to use the right format specifier .

I guess that makes sense. That would also suggest that using the wrong format/data type should be an ERROR and not a WARNING that doesn't even show up under compiler warnings: default.

You taught me my lesson for the day. I am going to leave it at compiler warnings: more from now on!

Now I can get back to programming. Thanks for pointing me in the right direction! My poor delapidated brain appreciates it!

if you activate the warning to max level then the compiler will terminate

cc1plus: some warnings being treated as errors
exit status 1

yes, it's debatable if it's a warning or a bug as the C++ language rules are not broken. the function's prototype is

void printf(const char *fmt, ...);

and you respected that.

The thing is that GCC, like many other C and C++ compilers, performs format string checking for functions like printf() when you provide format specifiers such as %d , %s , etc. and corresponding arguments. This is done through compiler warnings or errors and the mechanism for this checking is typically implemented within the compiler itself. It parses the format string and uses that information to verify the types of the arguments. This is very custom because printf has been around forever and it's such a common mistake.

If you're implementing your own variadic functions, the compiler won't do it for you.

'%llu' is for unsigned long long.

No, millis() is of type 'unsigned long'.

Thank you for "%llu". I later also realized that I got millis() wrong. I was getting confused by the fact that the ESP32 is 32 bit, where millis() is simply unsigned int and the ATMEGA328 which is 16 bit and millis() is unsigned long. I applied the up-scaling of the 328 to the ESP32 and had the delusion that millis() was 64 bit.
This type of cognitive failure is the reason I had to end a successful 40+ year career in the computer field. Now it is just a hobby...

1. ESP32 MCU uses 32-bit LX6 Microprocessor.

2. ATmega38P is an 8-bit Microcontroller.

3. When uploading of a sketch is complete (considering Arduino UNO Platform), an unsiged 32-bit (four consecutive RAM locations) counter known as millisCounter is auotomatically started and keeps counting every elaped 1-ms time period in the background on interrupt basis.

4. The millis() is a function that can be called upon at any time to read the present content of the millisCounter on-the-fly. The code is:
unsigned long int prsentMillis = millis();

millis is still defined as unsigned long. It's not ever going to be unsigned int. But it also won't be unsigned long long. If that makes it 64 bits on some cores then that's fine, but you still just call it unsigned long. You don't need to change the type to accommodate it.

on ESP32

on AVR

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