Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> I'd say C++ is the least suitable for "learn on the job" approach out of any language I can think of

Anecdote: I've got a couple languages and decades under my belt, and a very simple C/C++ Arduino project is making me doubt my sanity. Ex:

    Serial.printf("%s", String("Hello world...")); // Emits 4 nonsense bytes... But shorter literals work fine.
________________________

EDIT: To save readers some time--I didn't intend to nerd-snipe [0] y'all, honest!--the answer has been found, and it is probably:

    Serial.printf("%s", String("Hello world...").c_str() );
The shorter payloads like "Hello World" happen to give the desired results only as an accident of optimization code.

[0] https://xkcd.com/356/



Your problem is that whoever designed that Serial class is incompetent and should be shot and their body defiled in unspeakable ways.

The C-language varargs concept and the C++ object model are not compatible. C++ provides varargs only for backwards compatibility with legacy C libraries. New APIs written in C++ should not be using it because passing a C++ object through it is undefined behaviour. No amount of rationalizing (as all the comments downthread are doing) is of any value: undefined behavior is undefined.


Yet GCC will still apparently let you call this function without as much as a warning. This should be one of the easiest mistakes to find statically.

Clang at least errors out here.

C++ is a mess.


Well, if the designer of the class used the right annotations GCC will issue a warning if warnings are enabled. This is third-party code and nothing to do with GCC per se and the language itself does not require any kind of compile-time warning for undefined behaviour. Then again, most people seem to disable or ignore the warnings the toolchain gives them. You can make anything idiot-proof but there's always a bigger idiot.

It sounds more like a crappy library designed by people who don't know their craft. It's easier to blame the tools.


It’s Arduino so it’s C++ through the looking glass. Some unusual choices about core classes and syntactic sugar via a bit of preprocessing, plus the opaque error messages and core dumps we all know and love, but on a tiny device with a serial connection.


Is it even possible to core dump on AVR, with no storage and MMU?


Seg fault, I guess? It’s been a while.


AVR has no protection and no segments. I mean really, it seems to be true that new generation of programmers have zero understanding of hardware, nothing personal.


Nothing personal taken. If I was something more than a hobbyist wrt to hardware, then I might.

I was having this conversation with someone the other day regarding GenAI. The expectations for understanding the lowest-level concerns have changed generationally. Today's hardware wizard might prize their old-school understanding of fundamentals but would probably be rubbish in 1948.


It is completely legal to pass a C++ object through varargs.


Will gcc catch that? The GCC compiler knows about "printf" as a special case. But "Serial.printf" may not have a definition with the annotation GCC needs to check parameters.


also avr-gcc is several major versions behind, isn't it?


Since it's using the String class, this is likely not being compiled by avr-gcc. Or at least I hope OP isn't being so masochistic as to try to use String on a Mega328.


you're right, it turns out it's an esp32


I imagine you got that code snippet(or a similar example) from somewhere but to me the fairly obvious problem is the chafing between the C and C++ world. %s is for C style strings and I have to imagine that printf function is in the C world. The String(“Hello world…”) is an object in C++ world so expect weird behavior when you try to combine them. As you say in your edit, SSO will make this even weirder.


Random 4 bytes sounds like reading a pointer as something else, shouldn't be too hard to debug.


If you're really asking about that code snippet...

I don't know that library, but have you tried this?

  Serial.printf("%s", "Hello longer world");


Oh, that works, but the Arduino String library had some features I wanted. Its docs have an explicit example of putting a literal (an even longer one) into the constructor, so... Mysteries!

My default expectation is that it is a footgun that I do not understand.

The alternative is that I've stumbled across a very rare bug in some popular libraries, or else my hardware is cursed in a very specific and reproducible way.


Looking at [0], I see a "c_str()" method. Maybe give that a shot?

  Serial.printf("%s", String("Hello world...").c_str());
I actually don't see a "Serial.printf(...)" method in these docs [1], but I do see it here [2]. I'm not sure I'm looking at that right docs for your library though.

[0] https://www.arduino.cc/reference/en/language/variables/data-...

[1] https://www.arduino.cc/reference/en/language/functions/commu...

[2] https://docs.particle.io/reference/device-os/api/serial/prin...


Just to answer the mystery, it seems the foot-gun is that smaller String()s appear to work "by accident" due to an optimization, and I have to call a method on the String object before passing it onwards. [0]

> I actually don't see a "Serial.printf(...)" method

I think it's coming from the ESP32-specific libraries. Some cursory searching didn't find the spot, but it may be some magical preprocessor directive stuff.

[0] https://github.com/espressif/arduino-esp32/blob/7a82915de215...


Maybe what's happening is the `printf` function is interpreting the memory address of the `String` object as if it were a pointer to a char array. This leads to it printing seemingly random bytes (which are actually parts of the `String` object's internal data structure) instead of the string content you expect.


This sounds kinda plausible.

IIRC, some string implementations have separate implementations for very short strings vs. longer ones. Similar thing for vectors.

I also see some size-related logic in the (current?) String implementation [0] [1].

String is a class with no virtual methods, and its first data member is a pointer to the underlying buffer [2]. So if Stream.printf unintentionally type-puns String to char*, it might work out okay?

[0] https://github.com/arduino/ArduinoCore-API/blob/master/api/S...

[1] https://github.com/arduino/ArduinoCore-API/blob/master/api/S...

[2] https://github.com/arduino/ArduinoCore-API/blob/cb3ab4c90d71...


Although sizeof(String) > sizeof(char*), so any subsequent arguments to printf(...) would probably be screwed up.


Thanks, yeah, that seems to match this SO answer [0] which refers to some "small-string optimization" in the ESP32 libraries [1].

So basically the foot-gun is that smaller String()s appear to work "by accident".

[0] https://arduino.stackexchange.com/a/90332

[1] https://github.com/espressif/arduino-esp32/blob/7a82915de215...


It gets worse.

You're only printing 4 bytes because your string is sufficiently short, and the next byte it's reading is 0, since your capacity is small.

If your string were about 17 million bytes long (0x0101'0101 == 16843009 is the first value that causes problems), then your address, capacity, and size would all likely be nonzero, in which case your Arduino would just keep printing bytes until it lucked upon a zero, or overran its buffer so badly that it started trying to read different segments of memory and eventually segfaulted.


> your Arduino would just keep printing bytes until it lucked upon a zero, or overran its buffer so badly that it started trying to read different segments of memory and eventually segfaulted.

I don't think I've ever used an arduino core for something that had an MMU. It should happily keep printing all the way until the end of memory, and either stop there because it's zeros after, or wrap back to the start depending on the board. I have written code to dump out every byte in memory over serial before, just for kicks.


This is more of a C question than C++.

In a C++ library you would probably make the format function type safe. But both Clang and gcc have annotations you can add to functions using format strings so they are type-checked


eh forget all this coding minutiae, i dabbled with arduino here and there.

in my experience it's not to do with code. your serial/usb runs the bytes back through your debugger (typically IDE On the PC). Your IDE has config settings like baud rate +/- encoding that you have to config. If that IDE config is messed up (IDE Is presumably printing out your printf bytes over usb or whatever), then your output is bytes. Make sure your baud is correct.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: