16

I'm using a std::string to interface with a C-library that requires a char* and a length field:

std::string buffer(MAX_BUFFER_SIZE, '\0');
TheCLibraryFunction(&buffer[0], buffer.size());

However, the size() of the string is the actual size, not the size of the string containing actual valid non-null characters (i.e. the equivalent of strlen()). What is the best method of telling the std::string to reduce its size so that there's only 1 ending null terminator character no explicit null terminator characters? The best solution I can think of is something like:

buffer.resize(strlen(buffer.c_str()));

Or even:

char buffer[MAX_BUFFER_SIZE]{};
TheCLibraryFunction(buffer, sizeof(buffer));
std::string thevalue = buffer;

Hoping for some built-in / "modern C++" way of doing this.

EDIT

I'd like to clarify the "ending null terminator" requirement I mentioned previously. I didn't mean that I want 1 null terminator explicitly in std::string's buffer, I was more or less thinking of the string as it comes out of basic_string::c_str() which has 1 null terminator. However for the purposes of the resize(), I want it to represent the size of actual non-null characters. Sorry for the confusion.

void.pointer
  • 24,859
  • 31
  • 132
  • 243
  • 2
    Why do you need to resize it? Does your C function get upset if you're passing it more than the length parameter indicates? – molbdnilo Mar 21 '18 at 15:40
  • 4
    nothing wrong with `buffer.resize(strlen(buffer.c_str()));` – doron Mar 21 '18 at 15:41
  • 1
    @void: If a C library takes a `char*`+size, then they almost always accept embedded NUL characters. That's *why* C APIs take string lengths. What you want to do is usually unnecessary. – Nicol Bolas Mar 21 '18 at 15:54
  • @NicolBolas This particular C function is writing to the specified buffer I give it, not reading from it. I left that detail out, sorry about that. I won't know exactly how many characters are written until after the function returns. – void.pointer Mar 21 '18 at 15:59
  • Option 3, where you don't make a string until after the call, seems best to me – Caleth Mar 21 '18 at 16:01
  • 1
    @void.pointer: Then why do you need to do this? `basic_string::size` will give you the size of the buffer that can be written to. You want to tell the C function how much space it has to write into the buffer. Why do you need to truncate your buffer? If the C function returns how many characters it has written, just `resize` it to that afterwards. – Nicol Bolas Mar 21 '18 at 16:01
  • 1
    @NicolBolas Because the size of the string after the call is technically a lie. I want it to be the size of actual characters, not the maximum size. If I use this string in a stream later, it writes out all the null characters as well which I don't want. In a way I'm abusing the size property of a string before the C function call to treat the string as a buffer with a maximum size. – void.pointer Mar 21 '18 at 16:03
  • @void.pointer as Nicol said: "*If the C function returns how many characters it has written, just resize it to that afterwards*". Does the C function do that? `std::string buffer(MAX_BUFFER_SIZE, '\0'); int size = TheCLibraryFunction(&buffer[0], buffer.size()); buffer.resize(size);` I would find it very odd if a function taking a `char*`+maxsize doesn't return the real size. If not, just use `strlen(c_str())` after the function exits: `std::string buffer(MAX_BUFFER_SIZE, '\0'); TheCLibraryFunction(&buffer[0], buffer.size()); buffer.resize(strlen(buffer.c_str()));` – Remy Lebeau Mar 21 '18 at 17:04

7 Answers7

15

Many ways to do this; but probably the one to me that seems to be most "C++" rather than C is:

str.erase(std::find(str.begin(), str.end(), '\0'), str.end());

i.e. Erase everything from the first null to the end.

Trenton McKinney
  • 56,955
  • 33
  • 144
  • 158
UKMonkey
  • 6,941
  • 3
  • 21
  • 30
  • 1
    [std::string::find](http://en.cppreference.com/w/cpp/string/basic_string/find) doesn't return an iterator. – O'Neil Mar 21 '18 at 15:46
  • 1
    What about `str.resize(str.find('\0'));`? – void.pointer Mar 21 '18 at 15:47
  • @O'Neil you're right - would mean we have to add str.begin() (which breaks if not found) to it; or use std::find as liliscent said – UKMonkey Mar 21 '18 at 15:49
  • I'm not sure whether I misunderstood OP's question, but this solution is flawed. If the original buffer is not null-terminated, the iterator is out of boundary. – llllllllll Mar 21 '18 at 15:50
  • @O'Neil Yeah that ran though my mind as well. Perhaps `MAX_BUFFER_SIZE+1` on the initial size, and then the previous snippet I gave would be safe. – void.pointer Mar 21 '18 at 15:50
  • I updated my question to clarify a misunderstanding. When I said I only wanted 1 trailing null terminator, I was implying the one that `std::string` manages & expects. So from the outside perspective, I don't want any explicit null terminators. Sorry for the confusion. – void.pointer Mar 21 '18 at 15:53
  • @void.pointer but std:string doesn't expect or require any null terminator – UKMonkey Mar 21 '18 at 15:54
  • @UKMonkey From the perspective of `c_str()` it does. Not concerned about the implementation too much. Not disagreeing with you, just stating that I miscommunicated what I intended to ask for. – void.pointer Mar 21 '18 at 15:55
  • @NicolBolas yes - it will - but I answered the question at the time. It has since changed; I've updated my answer to match. – UKMonkey Mar 21 '18 at 16:12
  • @void.pointer yes; c_str() will ensure that it provides a null terminated string; but this is NOT included in the size() call unless you explcitly add it to the string. ie `std::string derp("hello")` has a length of 5, not 6 – UKMonkey Mar 21 '18 at 16:13
6

You can do this:

buffer.erase(std::find(buffer.begin(), buffer.end(), '\0'), buffer.end());

Consider std::basic_string::erase has an overloading:

basic_string& erase( size_type index = 0, size_type count = npos );

A more succinct way:

buffer.erase(buffer.find('\0'));
llllllllll
  • 16,169
  • 4
  • 31
  • 54
2

You can use buffer.find('\0') instead of strlen(buffer.c_str())

Moeren
  • 169
  • 1
  • 6
2

Using your

std::string buffer(MAX_BUFFER_SIZE, '\0');
TheCLibraryFunction(&buffer[0], buffer.size());

To remove 0-terminators, simplest is:
buffer = buffer.c_str();
(also possibly fastest? - didn't test)

slashmais
  • 7,069
  • 9
  • 54
  • 80
1

I know this question already has some answers, but I also want to contribute with my answer, although this solution works for all null terminators only at the end of the string.

void rtrim_null(std::string& str) {
    str.erase(std::find_if(str.rbegin(), str.rend(), [](int character) {
        return '\0' != character;
    }).base(), str.end());
}
NutCracker
  • 11,485
  • 4
  • 44
  • 68
0

One Liner using boost::trim_if if you want to trim multiple characters (I wanted to remove \r, \n, \0 and ' ') from the string:

int main()
{
    std::string str("abcd \0 \r\n \0", 12);
    std::cout << "Str: " << str << std::endl;
    std::cout << "Length: " << str.length() << std::endl;

    boost::trim_if(str, boost::is_any_of(std::string("\r\n\0 ", 4)));

    std::cout << "Length: " << str.length() << std::endl;
    return 0;
}

Prints:

Str: abcd

Length: 12
Length: 4
abhiarora
  • 9,743
  • 5
  • 32
  • 57
0

I'd like to give you a very QUICK and EASY answer. Just do:

yourStringWithNullChars = yourStringWithNullChars.c_str();

then

yourStringWithNullChars.shrink_to_fit();

Done, no need to search and replace, no need for external libraries. Yes, it's tested, I use here on my program. ;)

Bye.