What header do I need for the safe string function strlcpy etc

I've seen a lot of stuff on the internet about the "safer" C++ character array functions such as strlcpy, strlcat etc. I'm new to Arduino programming so maybe I'm missing something obvious, but I can't get these functions to compile ("error: 'strlcpy' was not declared in this scope"). I presume I'm missing a .H file, but I've found nothing that tells me which one.

https://linux.die.net/man/3/strlcpy

I use strncpy() etc. instead, which is included in <string.h> for Arduino.

1 Like

There is another library for using strings that are much more safe than classical arrays of char.
This library is called SafeString.h

It would be a lowercase .h -- C++ is case-sensitive, as are some file systems. Might as well get in the habit.

Having said that, it depends on your board. Some platforms' string.h have it, and some don't.

void setup() {
  strncpy(nullptr, nullptr, 0);
  strlcpy(nullptr, nullptr, 0);
}

void loop() {}

Compiles on ESP32 and AVR Uno for example, but not R4.

Apparently it is about to be(?) added to POSIX, and therefore was added to glibc. So maybe in a few more years....

Fun implementation detail

size_t
__strlcpy (char *__restrict dest, const char *__restrict src, size_t size)
{
  size_t src_length = strlen (src);

  if (__glibc_unlikely (src_length >= size))
  {
    if (size > 0)
    {
	  /* Copy the leading portion of the string.  The last
	     character is subsequently overwritten with the NUL
	     terminator, but the destination size is usually a
	     multiple of a small power of two, so writing it twice
	     should be more efficient than copying an odd number of
	     bytes.  */
	  memcpy (dest, src, size);
	  dest[size - 1] = '\0';
    }
  }
  else
    /* Copy the string and its terminating NUL character.  */
    memcpy (dest, src, src_length + 1);
  return src_length;
}

4 posts were split to a new topic: SafeString library discussion again :slight_smile:

Can we please leave the discussion about the SafeString library to another topic. StefanL38's mention of it is OK, the discussion not so in my view.

Did you forget about your topic easy to understand examples of SafeString-function stoken? Post #5 gave a compare. You can ask a moderator to re-open it for further discussion of so desired.

I'm moving those post into a new conversation

1 Like

for the record, code with strlcpy() or strncpy() just works out of the box for me using the IDE. (the Arduino.h header that is injected automatically does that for you).

Start by just ...
#include <Arduino.h>

That gives you things like ...
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <math.h>

Giving you a lot of the "functionality" you are looking for.

Be warned, there are no "safe" functions in C(++). It is up to you to build in the "safety".

memcpy(), for example, copies whatever source to whatever destination with no limits, it is up to you to correctly set source, destination and "volume".

strlcpy() is considered safe because it ensures that the destination buffer is not overflowed. It copies a specified number of characters from the source string to the destination, making sure to always null-terminate the destination string, even if the source string is larger than the destination buffer and will report information that will let you know if everything was copied.

So in that sense it's safe, but you still indeed need to ensure that the destination buffer was large enough to avoid truncation of the data and working off incomplete data afterwards.

for memcpy() indeed you need to add checks yourself

void * memcpySafe(void *dest, const void *src, size_t n, size_t destSize) {
  if (dest == nullptr || src == nullptr || n > destSize)  return nullptr;  // Error handling
  return memcpy(dest, src, n);
}

That was my point regarding C in general. Often, by design, there are few "safety" features.

strcpy() could be considered as a call to memcpy() after the NULL terminator position has been determined.

I thought the "safety" warning might be of help some that are new to programming. "C" doesn't care what you tell it to do :wink:

fair

it seems OP was already aware though.

Ugh. Reads the source string at least twice.
Might be reasonable on big machines with fast caches and big strings that memcpy() has big advantages, but it seems like a poor choice for small microcontrollers. :frowning:
(Might be overridden by per-cpu code hidden elsewhere in the gcc build process?)

Wow, I seem to have sparked off quite a discussion :). However, I omitted to mention that I am using a UNO R4 WIFI, so I think the important answer is by @kenb4 - these functions are not available for the R4. Ho hum. I originally used the String class, but couldn't get it to work, and as I am also having terrible trouble getting the IDE's debugger to work properly, I thought I'd try with the C++ classics (I was a C++ dev for a long time, but moved to C# about 15 years ago, so I'm a bit spoilt :)). Anyway, thanks to all.

This should be raised as a bug….

It's especially weird because SAMD and UNOR4 include the exact same string.h file, and it works fine on SAMD...

inclusion of strlcpy() is "conditional" depending on __BSD_VISIBLE:

#if __BSD_VISIBLE
size_t	_EXFUN(strlcat,(char *, const char *, size_t));
size_t	_EXFUN(strlcpy,(char *, const char *, size_t));
#endif

(I could swear that they'd use the same "newlib" as well, but adding the declations explicitly to a sketch does result in a link error...)

The lack if inclusion is the result of -D_XOPEN_SOURCE=700 in platform.txt. Other cores that I have at the moment don't have it.

Did you forget extern "C"?

extern "C" {
  size_t strlcat(char *, const char *, size_t);
  size_t strlcpy(char *, const char *, size_t);
}
1 Like

I did :frowning: It now compiles :+1:

Yep, me too! Thanks for noticing!

I wonder what THAT is supposed to accomplish? (700 seems to be the "maximum exposure" provided by _XOPEN_SOURCE, and it also seems to be the default, but defining it prevents __BSD_VISIBLE from being defined (which would also normally be the default.)

A useful discussion I think. In the end I went back to using String, which may have its problems but suits me better as a C# programmer (no need to worry about overruns). FWIW the problem I had with String was as follows:

int c = Serial.read();
if (c != '\n') inputString.concat(c);

I had missed the fact that String.concat(int) converts the integer value to a string. Serial.read() returns an int so that it can return -1 if there is no data, which IMHO is a bad interface (though a common C++ one). Something like bool Serial.read(char& c) would be more modern (C# would use "out"). The solution, of course, is to make c a char rather than an int, then it works (although, also IMHO, it shouldn't compile without an explicit cast).