A question about best practice with Strings (capital S).

Hi there,

I would like to try and keep some best practices when working with Arduino and c++. I have come across an issue. As far as I understand Strings are :sob: as eloquently, albeit slightly condescendingly exemplified by PaulS:

String aStupidWasteOfResource = "Pissing away resources uselessly";
char copy[50];
aStupidWasteOfResource.toCharArray(copy, 50);

I happen to be using the ArduinoBLE library and I want to get a unique identifier for naming my device. I found a good way to do this would be to use BLE.address(), however, this returns a String.

How would I go about naming my devices uniquely using that String while still using the C string library, without incurring the wrath of veterans?

More on the evils of Arduino Strings...

If you are using the nano BLE then it has a lot more memory than an Uno or Mega so you may get away with using the String class.

Otherwise convert the String to a char array using the .toCharArray() method as illustrated by @PaulS

...R

Steen_Petersen:
How would I go about naming my devices uniquely using that String while still using the C string library, without incurring the wrath of veterans?

while String is a class which implies its C++, there is no library for c strings; it's a collection of routines that been around since the beginning of C.

why can't you obtain a BLE String and get a reference to it using c_str()

Thank you for pointing me in the right direction, I have now tried finding a way by reading some c++ documentation [1][2] and the aforementioned article (highly recommend it) and come up with this:

#include <ArduinoBLE.h> // BLE library

char tmp[30];
char deviceName[5] = "NAME"; // extra char for o?

void setup() {

  // begin initialization
  if (!BLE.begin()) {
    while (1);
  }
  
  Serial.begin(9600);
  strncpy(tmp, strcat(deviceName, BLE.address().c_str()), sizeof(tmp));
  BLE.setDeviceName(tmp);
}

void loop() {
  // put your main code here, to run repeatedly:
  Serial.print("Local address is: ");
  Serial.println(tmp); 
}

Would this be an alright way to do it? I mean the loop is just for testing purposes so I can see what it writes.

Well once the cat is out of the bad (ie you use a library using the String class) you are no longer in control anymore.

I would say go check source code to see how it's done

String BLEDevice::address() const
{
  char result[18];
  sprintf(result, "%02x:%02x:%02x:%02x:%02x:%02x", _address[5], _address[4], _address[3], _address[2], _address[1], _address[0]);

  return result;
}

So when you call this you get indeed a dynamically instantiated String built out of a... cString.

if you use it transiently and do not dynamically instantiate/allocate anything before your String goes out of scope, then likely not an issue. Same if you keep that in a global variable then it will never go out of scope and you won't risk poking a hole in the heap (but of course use memory for something that's available somewhere else already)

Too bad they did not choose to make the _address array public, you could modify the library and then just access the 6 byte array to get the address in raw format.

gcjr:
there is no library for c strings; it's a collection of routines that been around since the beginning of C.

to be fair they were called libraries long before the first arduino arrived... (have a look in stdlib.h and string.h - but indeed it's a collection of functions)

gcjr:
why can't you obtain a BLE String and get a reference to it using c_str()

you can of course but the String would have already been instantiated, allocated, copied. and if you just keep a handle on the underlying cString, you still have the String in memory anyway and all the code that goes with it....

It's not as bad on such an architecture though as you have way more memory.

J-M-L:
to be fair they were called libraries long before the first arduino arrived... (have a look in stdlib.h and string.h - but indeed it's a collection of functions)

i don't need to link to a c string library like i do to the math library. a .h file does not mean there's a library. look under /usr/lib for C libraries

look under /usr/lib for C libraries

But not many Arduino libraries there

TheMemberFormerlyKnownAsAWOL:
But not many Arduino libraries there

are you using the Arduino environment as a standard?

My point was about you saying

“there is no library for c strings“

Which I respectfully disagree with

that’s what those were called long before Arduino And some have even lib in their name

J-M-L:
that’s what those were called long before Arduino And some have even lib in their name

and perhaps long before PCs and C++. I understand that library can refer to many things

unix/linux library files have a .a extension and are pre-compiled and only the relevant functions are extracted during linking. The Linux libc.a is 500K+ bytes I do see some library files under the arduino directory, including libc.a (250k) and libm.a.

From what i've seen, when you install an Arduino library such as SoftwareSerial, a sub directory is created and typically under the /src are some .h and .cpp file. I believe those file are compiled each time you build you app. in other words they aren't a .a library file (allowing them to be built for different processor architectures)

for example, when i look under /users/lenovo/AppData/Local/Temp/arduino_build_999036, for a fairly simple Arduino program, i find

Temp ls -R arduino_build_999036/
arduino_build_999036/:
Tst.ino.eep  Tst.ino.with_bootloader.hex  includes.cache  sketch
Tst.ino.elf  build.options.json           libraries
Tst.ino.hex  core                         preproc

arduino_build_999036/core:
CDC.cpp.d              IPAddress.cpp.o     WMath.cpp.d    wiring.c.d
CDC.cpp.o              PluggableUSB.cpp.d  WMath.cpp.o    wiring.c.o
HardwareSerial.cpp.d   PluggableUSB.cpp.o  WString.cpp.d  wiring_analog.c.d
HardwareSerial.cpp.o   Print.cpp.d         WString.cpp.o  wiring_analog.c.o
HardwareSerial0.cpp.d  Print.cpp.o         abi.cpp.d      wiring_digital.c.d
HardwareSerial0.cpp.o  Stream.cpp.d        abi.cpp.o      wiring_digital.c.o
HardwareSerial1.cpp.d  Stream.cpp.o        core.a         wiring_pulse.S.d
HardwareSerial1.cpp.o  Tone.cpp.d          hooks.c.d      wiring_pulse.S.o
HardwareSerial2.cpp.d  Tone.cpp.o          hooks.c.o      wiring_pulse.c.d
HardwareSerial2.cpp.o  USBCore.cpp.d       main.cpp.d     wiring_pulse.c.o
HardwareSerial3.cpp.d  USBCore.cpp.o       main.cpp.o     wiring_shift.c.d
HardwareSerial3.cpp.o  WInterrupts.c.d     new.cpp.d      wiring_shift.c.o
IPAddress.cpp.d        WInterrupts.c.o     new.cpp.o

arduino_build_999036/libraries:

arduino_build_999036/preproc:
ctags_target_for_gcc_minus_e.cpp

arduino_build_999036/sketch:
Tst.ino.cpp  Tst.ino.cpp.d  Tst.ino.cpp.o

there are various standard components: HardwareSerial, Stream, Print, Tone, ... that are all re-compiled from the .cpp file. These could easily have been precompiled into a .a file. But Arduino programs are typically small and the overhead to re-compile is small

however, the string routines are in a library, the C standard library, libc.a. However, it does not solely contain the string routines. The Arduino version also includes File, time, some math (e.g. sin) functions, over 135. The standard (non Arduino) unix version has ~1700 functions.

so while the c string functions are in a library, i wouldn't say there is a string library. They are just standard c functions. When I build .cpp programs on my laptop, i don't need to specify any extra libraries. I would need to specify the math library, -lm if I were using some math functions. And as far as I know, you don't need to link to any special library to use Strings. So maybe neither is a library.

perhaps for you it's just semantics. For me there are distinctions that are sometime necessary to build code.

TheMemberFormerlyKnownAsAWOL:
But not many Arduino libraries there

i find 418 .a files in sub-directories under Arduino/hardware

if you use it transiently and do not dynamically instantiate/allocate anything before your String goes out of scope, then likely not an issue.

You can instantiate all you like, but you have to make sure that whatever you instantiate, will go out of scope as well.

Same if you keep that in a global variable then it will never go out of scope and you won't risk poking a hole in the heap (but of course use memory for something that's available somewhere else already)

there is the .reserve() member function that does exactly that, reserves a part of memory for a String and if the String in question never exceeds that size, no fragments can occur.

gcjr:
i find 418 .a files in sub-directories under Arduino/hardware

I must have a seriously non-standard installation; I don't even have a /usr/lib/Arduino directory.

In fact "find /usr/lib -name "Arduino" finds...nothing.

Spooky.

What am I doing wrong?
Or right?

on Unix/Linux or from my cygwin window on my laptop, library files are under /usr/lib/ (which is actually c:/Tools/Cygwin/usr/lib/)

on my laptop I installed Arduino under c:/Tools (or /tools from cygwin). So look under the hardware/ directory under your Arduino/

Or right?

learn a bit more about the Arduino environment (this gave me a chance to)

on my laptop I installed Arduino under c:

Linux doesn't have "c:"

That must be what I'm doing right

Hum ... The .a is for archive not for library :grin:

(I’m pulling your leg - Yes there are many things you can call a library)

Deva_Rishi:
You can instantiate all you like, but you have to make sure that whatever you instantiate, will go out of scope as well.

there is the .reserve() member function that does exactly that, reserves a part of memory for a String and if the String in question never exceeds that size, no fragments can occur.

Both fair points. I cut corners indeed in my answer.

TheMemberFormerlyKnownAsAWOL:
Linux doesn't have "c:"

That must be what I'm doing right

Idk about that

Power_Broker:
Idk about that

I do.

What's funny is, I used to work for a customer of one of gcjr's former employer.

We were advised to run their Linux tools on Windows, under emulation (VirtualBox).

Laugh?

I thought I'd never start.