Casting a negative char to an int results in a positive number. The results differ depending on the platform.
#include <Arduino.h>
void setup() {
Serial.begin(115200);
Serial.println();
char c = -61;
if(c != -61) { Serial.print("Char is not -61, but "); Serial.println((int)c); }
else Serial.println("Char is -61");
int i = c;
if(i != -61) { Serial.print("Int is not -61, but "); Serial.println(i); }
else Serial.println("Int is -61");
}
void loop() {
delay(100);
}
Running on Arduino IDE, Arduino Uno
Char is -61
Int is -61
Running on Arduino IDE, Nodemcu ESP-12E
Char is not -61, but 195
Int is not -61, but 195
Cloned newest release. Added file to TEST_CPP_FILES. Used make test to run.
#include <catch.hpp>
#include <iostream>
TEST_CASE("get char") {
char c = -61;
if(c != -61) std::cout << "Char is not -61, but " << (int)c << std::endl;
else std::cout << "Char is -61" << std::endl;
int i = c;
if(i != -61) std::cout << "Int is not -61, but " << i << std::endl;
else std::cout << "Int is -61" << std::endl;
}
Char is -61
Int is -61
int is 32 bit, char is 8 bit. The 2's complement of -61 is to 195.
It's not a bug, now google and learn.
Char is signed, int is signed. Char goes from -128 to 127, int from -2,147,483,648 to 2,147,483,647. A char can be perfectly represented in an int. Casting a smaller (byte size) signed type to a bigger signed type results in a signed value. At least thats how (almost) all computer languages work. As you can see the test on the Arduino Uno and on the host work as expected. Only the ESP12E is the odd one out
Casting a negative int to a long also works as expected. Only char doesnt. I suspect the char is implemented as a uint8_t on the ESP12
GCC gives a warning when you compile this. That's the root of your problem, you're not really doing a comparison like you think you are in the first case. Please look at GCC's typing and condition checking.
/home/earle/Arduino/sketch_apr23a/sketch_apr23a.ino: In function 'void setup()':
/home/earle/Arduino/sketch_apr23a/sketch_apr23a.ino:7:14: warning: comparison is always true due to limited range of data type [-Wtype-limits]
if(c != -61) Serial.printf("Char is not -61, but %d\n", (int)c);
^
Sorry, I'm lost on this one. A char is perfectly able to represent negative numbers. Also, how does that explain the different results on the ESP, Uno and the host?
To make my point even more clear:
Sketch
void setup() {
Serial.begin(115200);
Serial.println();
char c = -62;
unsigned char u = -62;
Serial.println(c == u);
}
Output:
10This shows that a signed and unsigned char are equal on an ESP and not equal on an Arduino Uno. Mybe this is a Arduino IDE issue?
Even earlephilhower's comment should prove this. GCC is warning that a char can never be negative. Again, that only is the case if it is implemented as unsigned. But that is different from the Uno
https://stackoverflow.com/questions/436513/char-signed-char-char-unsigned-char
It's implementation-defined.
Thx, thats the answer I was looking for. So the Uno implements a plain char as a signed char and the ESP as a unsigned char. I changed the second Sketch to this:
void setup() {
Serial.begin(115200);
Serial.println();
signed char c = -62;
unsigned char u = -62;
Serial.println(c == u);
}
Now the ESP and Uno give the same result.
But this would mean everywhere in this repo where a plain char is used, the code may not be portable between ESP and Uno
Example:
class MockStream : public Stream {
private:
String mStr;
const char* mCstr;
public:
MockStream(String str) {
mStr = str;
mCstr = mStr.c_str();
}
virtual int available() {
if(*mCstr == 0) return 0;
long charsRead = mCstr - mStr.c_str();
return mStr.length() - charsRead;
}
virtual int read() {
return *mCstr == 0 ? -1 : *mCstr++;
}
virtual int peek() {
return *mCstr;
}
virtual size_t write(uint8_t) {
return 1;
}
};
void setup() {
Serial.begin(115200);
Serial.println();
MockStream stream("[盲枚眉]");
Serial.println(stream.readString());
}
Output:
[][盲枚眉]Plain char is not guaranteed to be portable across different platforms. The signedness of char is implementation defined, and has always been. Traditionally, i.e. Historically, older compilers used signed char as default, because the ascii table only went up to 127. Then there was the extended ascii table which went up to 255, so that habit changed to unsigned char, mostly with 32bit cpus, and then spread back to uCs.
If you need portability, don't rely on implementation defined types, but use uint8/int8 etc to make sure you get what you think.
Also, don't rely on implicit casts, the compiler warning is there for good reasons.
Thank you all for explaining this to me. I come from Java and never had that issue with C/C++ until now (seems that in embedded you have to be WAY more careful). I thought all primitive types were defined in C/C++, turns out only char is not (kinda a trap if you ask me :)).
Just a couple of points to end this:
Changing the sketch to this fixed my issues:
class MockStream : public Stream {
private:
String mStr;
const char* mCstr;
public:
MockStream(String str) {
mStr = str;
mCstr = mStr.c_str();
}
virtual int available() {
if(*mCstr == 0) return 0;
long charsRead = mCstr - mStr.c_str();
return mStr.length() - charsRead;
}
virtual int read() {
return (unsigned char)*mCstr == 0 ? -1 : *mCstr++;
}
virtual int peek() {
return (unsigned char)*mCstr;
}
virtual size_t write(uint8_t) {
return 1;
}
};
Now Uno, ESP and host do exactly the same.
(Also, if you have the time to answer this, WHERE does the compiler ever throw that error, neither using Make on host nor the Arduino IDE throw that error. I guess you compiled the .ino file in the command line with gcc? Never done that, but I guess I should have a look at it)