strncpy equivalent for std::string?

9.1k views Asked by At

Is there an exact equivalent to strncpy in the C++ Standard Library? I mean a function, that copies a string from one buffer to another until it hits the terminating 0? For instance when I have to parse strings from an unsafe source, such as TCP packets, so I'm able to perform checks in length while coping the data.

I already searched a lot regarding this topic and I also found some interesting topics, but all of those people were happy with std::string::assign, which is also able to take a size of characters to copy as a parameter. My problem with this function is, that it doesn't perform any checks if a terminating null was already hit - it takes the given size serious and copies the data just like memcpy would do it into the string's buffer. This way there is much more memory allocated and copied than it had to be done, if there were such a check while coping. 

That's the way I'm working around this problem currently, but there is some overhead I'd wish to avoid:

    // Get RVA of export name
    const ExportDirectory_t *pED = (const ExportDirectory_t*)rva2ptr(exportRVA);
    sSRA nameSra = rva2sra(pED->Name);

    // Copy it into my buffer
    char *szExportName = new char[nameSra.numBytesToSectionsEnd];
    strncpy(szExportName, 
            nameSra.pSection->pRawData->constPtr<char>(nameSra.offset),
            nameSra.numBytesToSectionsEnd);
    szExportName[nameSra.numBytesToSectionsEnd - 1] = 0;

    m_exportName = szExportName;
    delete [] szExportName;

This piece of code is part of my parser for PE-binaries (of the routine parsing the export table, to be exact). rva2sra converts a relative virtual address into a PE-section relative address. The ExportDirectory_t structure contains the RVA to the export name of the binary, which should be a zero-terminated string. But that doesn't always have to be the case - if someone would like it, it would be able to omit the terminating zero which would make my program run into memory which doesn't belong to the section, where it would finally crash (in the best case...).

It wouldn't be a big problem to implement such a function by myself, but I'd prefer it if there were a solution for this implemented in the C++ Standard Library.

7

There are 7 answers

1
Seth Carnegie On BEST ANSWER

If you know that the buffer you want to make a string out of has at least one NUL in it then you can just pass it to the constructor:

const char[] buffer = "hello\0there";

std::string s(buffer);

// s contains "hello"

If you're not sure, then you just have to search the string for the first null, and tell the constructor of string to make a copy of that much data:

int len_of_buffer = something;
const char* buffer = somethingelse;

const char* copyupto = std::find(buffer, buffer + len_of_buffer, 0); // find the first NUL

std::string s(buffer, copyupto);

// s now contains all the characters up to the first NUL from buffer, or if there
// was no NUL, it contains the entire contents of buffer

You can wrap the second version (which always works, even if there isn't a NUL in the buffer) up into a tidy little function:

std::string string_ncopy(const char* buffer, std::size_t buffer_size) {
    const char* copyupto = std::find(buffer, buffer + buffer_size, 0);

    return std::string(buffer, copyupto);
}

But one thing to note: if you hand the single-argument constructor a const char* by itself, it will go until it finds a NUL. It is important that you know there is at least one NUL in the buffer if you use the single-argument constructor of std::string.

Unfortunately (or fortunately), there is no built in perfect equivalent of strncpy for std::string.

3
AudioBubble On

Use the class' constructor:

string::string str1("Hello world!");
string::string str2(str1);

This will yield an exact copy, as per this documentation: http://www.cplusplus.com/reference/string/string/string/

4
BЈовић On

std::string has a constructor with next signature that can be used :

string ( const char * s, size_t n );

with next description:

Content is initialized to a copy of the string formed by the first n characters in the array of characters pointed by s.

0
Karel Petranek On

The std::string class in STL can contain null characters within the string ("xxx\0yyy" is a perfectly valid string of length 7). This means that it doesn't know anything about null termination (well almost, there are conversions from/to C strings). In other words, there's no alternative in the STL for strncpy.

There are a few ways to still accomplish your goal with a shorter code:

const char *ptr = nameSra.pSection->pRawData->constPtr<char>(nameSra.offset);
m_exportName.assign(ptr, strnlen(ptr, nameSra.numBytesToSectionsEnd));

or

const char *ptr = nameSra.pSection->pRawData->constPtr<char>(nameSra.offset);
m_exportName.reserve(nameSra.numBytesToSectionsEnd);
for (int i = 0; i < nameSra.numBytesToSectionsEnd && ptr[i]; i++) 
  m_exportName += ptr[i];
1
Keith Thompson On

Is there an exact equivalent to strncpy in the C++ Standard Library?

I certainly hope not!

I mean a function, that copies a string from one buffer to another until it hits the terminating 0?

Ah, but that's not what strncpy() does -- or at least it's not all it does.

strncpy() lets you specify the size, n, of the destination buffer, and copies at most n characters. That's fine as far as it goes. If the length of the source string ("length" defined as the number of characters preceding the terminating '\0') exceeds n, the destination buffer is padded with additional \0's, something that's rarely useful. And if the length if the source string exceeds n, then the terminating '\0' is not copied.

The strncpy() function was designed for the way early Unix systems stored file names in directory entries: as a 14-byte fixed-size buffer that can hold up to a 14-character name. (EDIT: I'm not 100% sure that was the actual motivation for its design.) It's arguably not a string function, and it's not just a "safer" variant of strcpy().

You can achieve the equivalent of what one might assume strncpy() does (given the name) using strncat():

char dest[SOME_SIZE];
dest[0] = '\0';
strncat(dest, source_string, SOME_SIZE);

This will always '\0'-terminate the destination buffer, and it won't needlessly pad it with extra '\0' bytes.

Are you really looking for a std::string equivalent of that?

EDIT : After I wrote the above, I posted this rant on my blog.

0
Steve Ward On

There is no built-in equivalent. You have to roll your own strncpy.

#include <cstring>
#include <string>

std::string strncpy(const char* str, const size_t n)
{
    if (str == NULL || n == 0)
    {
        return std::string();
    }

    return std::string(str, std::min(std::strlen(str), n));
}
0
Julien-L On

The string's substring constructor can do what you want, although it's not an exact equivalent of strncpy (see my notes at the end):

std::string( const std::string& other, 
          size_type pos, 
          size_type count = std::string::npos,
          const Allocator& alloc = Allocator() );

Constructs the string with a substring [pos, pos+count) of other. If count == npos or if the requested substring lasts past the end of the string, the resulting substring is [pos, size()).

Source: http://www.cplusplus.com/reference/string/string/string/

Example:

#include <iostream>
#include <string>
#include <cstring>
int main ()
{
    std::string s0 ("Initial string");
    std::string s1 (s0, 0, 40); // count is bigger than s0's length
    std::string s2 (40, 'a');   // the 'a' characters will be overwritten
    strncpy(&s2[0], s0.c_str(), s2.size());
    std::cout << "s1: '" << s1 << "' (size=" << s1.size() << ")" << std::endl;
    std::cout << "s2: '" << s2 << "' (size=" << s2.size() << ")" << std::endl;
    return 0;
}

Output:

s1: 'Initial string' (size=14)
s2: 'Initial string' (size=40)

Differences with strncpy:

  • the string constructor always appends a null-terminating character to the result, strncpy does not;
  • the string constructor does not pad the result with 0s if a null-terminating character is reached before the requested count, strncpy does.