tags:

views:

432

answers:

9

I want to copy a string into a char array, and not overrun the buffer.

So if I have a char array of size 5, then I want to copy a maximum of 5 bytes from a string into it.

what's the code to do that?

A: 

Use strncpy.

Reference: clickie

Francisco P.
`strcpy` doesn't prevent overrunning the buffer.
Arkku
+2  A: 

Update: Thought I would try to tie together some of the answers, answers which have convinced me that my own original knee-jerk strncpy response was poor.

First, as AndreyT noted in the comments to this question, truncation methods (snprintf, strlcpy, and strncpy) are often not a good solution. Its often better to check the size of the string string.size() against the buffer length and return/throw an error or resize the buffer.

If truncation is OK in your situation, IMHO, strlcpy is the best solution, being the fastest/least overhead method that ensures null termination. Unfortunately, its not in many/all standard distributions and so is not portable. If you are doing a lot of these, it maybe worth providing your own implementation, AndreyT gave an example. It runs in O(result length). Also the reference specification returns the number of bytes copied, which can assist in detecting if the source was truncated.

Other good solutions are sprintf and snprintf. They are standard, and so are portable and provide a safe null terminated result. They have more overhead than strlcpy (parsing the format string specifier and variable argument list), but unless you are doing a lot of these you probably won't notice the difference. It also runs in O(result length). snprintf is always safe and that sprintf may overflow if you get the format specifier wrong (as other have noted, format string should be "%.<N>s" not "%<N>s"). These methods also return the number of bytes copied.

A special case solution is strncpy. It runs in O(buffer length), because if it reaches the end of the src it zeros out the remainder of the buffer. Only useful if you need to zero the tail of the buffer or are confident that destination and source string lengths are the same. Also note that it is not safe in that it doesn't necessarily null terminate the string. If the source is truncated, then null will not be appended, so call in sequence with a null assignment to ensure null termination: strncpy(buffer, str.c_str(), BUFFER_LAST); buffer[BUFFER_LAST] = '\0';

academicRobot
@academicRobot: In general case whether `strncpy` is faster or slower will depend on the relative sizes of the source string and the target buffer. Since `strncpy` does lots of wasted work in case of a large buffer, in general case misused `strncpy` is not only slower, but *catastropically slower*, orders of magnitude slower. The only thing that saves the day in this example is unrealistically small target buffer (only 5 chars).
AndreyT
@academicRobot: You also skewed the test results by insisting on a source string that is known to be longer than the buffer. Testing for such string alone is absolutely meaningless.
AndreyT
Finally, your conclusion is totally bogus. `sprintf`/`snprintf` is indeed not the most efficient function for obvious reasons. But that only means that one has to prefer using a `strlcpy`-like function, not the virtually useless `strncpy`. Finally, the importance of performance in a truncation-enabled string copying context is another issue. String truncation seen as something acceptable usually indicated user-interface application. Who needs performance in user interface?
AndreyT
@AndreyT Thank you sir, may I please have another! :) You are right on every point, except I won't concede the last (just for user interfaces, really?!?!). But using an strlcpy like function is probably the best option.
academicRobot
Well, think of it: you copy a string and you agree to lose a portion of that string (if it's too long). I.e. the data you copy gets distorted/corrupted/damaged/truncated (choose to your taste). In which context can this be acceptable? They only context I can come up with is a user-interface one: you tell user something very long and that something doesn't suffer much if you cut it a bit (like a list of errors, for example, when just the first one is enough). Can you come up with another context when the truncated data is OK?
AndreyT
@AndreyT Asking for another is just an American idiom. See Animal House (1978).
academicRobot
It is not about your "asking for another" reference. It is about your "user interfaces" remark.
AndreyT
@AndreyT ...but since you gave it to me anyway, I'd say you're right in the sense that in some situations using truncation methods smells like a bad choice. But I wouldn't be so grandiose as to say the only situation where it is appropriate is UI.
academicRobot
+1  A: 
std::string my_string("something");
char* my_char_array = new char[5];
strncpy(my_char_array, my_string.c_str(), 4);
my_char_array[4] = '\0'; // my_char_array contains "some"

With strncpy, you can copy at most n characters from the source to the destination. However, note that if the source string is at most n chars long, the destination will not be null terminated; you must put the terminating null character into it yourself.

A char array with a length of 5 can contain at most a string of 4 characters, since the 5th must be the terminating null character. Hence in the above code, n = 4.

Péter Török
+2  A: 

Some nice libc versions provide non-standard but great replacement for strcpy(3)/strncpy(3) - strlcpy(3).

If yours doesn't, the source code is freely available here from the OpenBSD repository.

Nikolai N Fetissov
A: 
std::string str = "Your string";
char buffer[5];
strncpy(buffer, str.c_str(), sizeof(buffer)); 
buffer[sizeof(buffer)-1] = '\0';

The last line is required because strncpy isn't guaranteed to NUL terminate the string (there has been a discussion about the motivation yesterday).

If you used wide strings, instead of sizeof(buffer) you'd use sizeof(buffer)/sizeof(*buffer), or, even better, a macro like

#define ARRSIZE(arr)    (sizeof(arr)/sizeof(*(arr)))
/* ... */
buffer[ARRSIZE(buffer)-1]='\0';
Matteo Italia
+6  A: 

First of all, strncpy is almost certainly not what you want. strncpy was designed for a fairly specific purpose. It's in the standard library almost exclusively because it already exists, not because it's generally useful.

Probably the simplest way to do what you want is with something like:

sprintf(buffer, "%.4s", your_string.c_str());

Unlike strncpy, this guarantees that the result will be NUL terminated, but does not fill in extra data in the target if the source is shorter than specified (though the latter isn't a major issue when the target length is 5).

Jerry Coffin
+1 for unique answer. But, isn't this a lot of unnecessary overhead relative to `strncpy(buffer, str.c_str(), 4); buffer[4] = '\0';`?
academicRobot
@academicRobot: Have you tested it, or noticed the difference? :) I'd prefer the `sprintf` solution, it's a bit more straightforward. Only when performance is lacking would I profile, maybe find this to be a problem, test it with `strncpy`, and maybe find it works better.
GMan
Prefer the safe version, `snprintf`, which lets you specify the target buffer size.
jweyrich
Isn't `%4s` a minimum length, not a maximum one?
Dennis Zickefoose
Or get a copy of strlcpy and use that. It guarantees NULL termination.
Mike Weller
@academicRobot: In general case whether `strncpy` is faster or slower will depend on the relative sizes of the source string and the target buffer. Since `strncpy` does lots of wasted work in case of a large buffer, in general case misused `strncpy` is not only *slower*, but *catastropically slower*, orders of magnitude slower. The only thing that saves the day in this example is unrealistically small target buffer (only 5 chars).
AndreyT
It guarantees null termination, actually protects against buffer overruns, which is what this question is about, *and* doesn't run the risk of mangling the format string. `sprintf` is about the worst possible solution to this problem.
Dennis Zickefoose
@Jerry Coffin: Isn't it supposed to be `%.4s`?
AndreyT
@AndreyT +1 Too right. Thanks pointing this out. Excuse me while I try to get this foot out of my mouth...
academicRobot
I forgot `%.4s` works that way, so `sprintf` isn't the worst possible solution. But that typo does illustrate why a more expressive solution should be preferred.
Dennis Zickefoose
@jweyrich: since we're specifying only one "conversion", and specifying the maximum size for it, `snprintf` provides no advantage in this case.
Jerry Coffin
@AndreyT:oops, yes, should be .4s.
Jerry Coffin
+5  A: 

Use function strlcpy if your implementation provides one (the function is not in the standard C library), yet it is rather widely accepted as a de-facto standard name for a "safe" limited-length copying function for zero-terminated strings.

If your implementation does not provide strlcpy function, implement one yourself. For example, something like this might work for you

char *my_strlcpy(char *dst, const char *src, size_t n)
{
  assert(dst != NULL && src != NULL);

  if (n > 0)
  {
    char *pd;
    const char *ps;

    for (--n, pd = dst, ps = src; n > 0 && *ps != '\0'; --n, ++pd, ++ps)
      *pd = *ps;

    *pd = '\0';
  }

  return dst;
}

(Actually, the de-facto accepted strlcpy returns size_t, so you might prefer to implement the accepted specification instead of what I did above).

Beware of the answers that recommend using strncpy for that purpose. strncpy is not a safe limited-length string copying function and is not supposed to be used for that purpose. While you can force strncpy to "work" for that purpose, it is still akin to driving woodscrews with a hammer.

AndreyT
A: 

If you always have a buffer of size 5, then you could do:

std::string s = "Your string";
char buffer[5]={s[0],s[1],s[2],s[3],'\0'};

Edit: Of course, assuming that your std::string is large enough.

Peter Jansson
This does not scale at all to buffers of arbitrary size.
Adam Rosenfield
Right. That's why I wrote "always have a buffer of size 5".
Peter Jansson
+2  A: 

This is exactly what std::string's copy function does.

#include <string>
#include <iostream>

int main()
{

    char test[5];
    std::string str( "Hello, world" );

    str.copy(test, 5);

    std::cout.write(test, 5);
    std::cout.put('\n');

    return 0;
}

If you need null termination you should do something like this:

str.copy(test, 4);
test[4] = '\0';
Charles Bailey
+1: For using std::string to do the legwork.
Johnsyweb