Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the best alternative to strncpy()?

Tags:

c

strncpy

The function strncpy() doesn't always null terminate so I want to know what is the best alternative that always null terminates? I want a function that if:

strlen(src) >= n /*n is the number of characters to be copied from source*/

there's no need to add further code like this:

buf[sizeof(buf)-1] = 0; 
like image 398
dems98 Avatar asked Jan 26 '17 08:01

dems98


People also ask

Which is better strcpy or strncpy?

strcpy( ) function copies whole content of one string into another string. Whereas, strncpy( ) function copies portion of contents of one string into another string. If destination string length is less than source string, entire/specified source string value won't be copied into destination string in both cases.

What is the difference between strncpy and Strlcpy?

Like strncpy, strlcpy takes the destination's size as a parameter and will not write more than that many bytes, to prevent buffer overflow (assuming size is correct). But, unlike strncpy, strlcpy always writes a single NUL byte to the destination (if size is not zero).

What is the difference between strncpy and Strncpy_s?

strcpy_s() is a security enhanced version of strcpy() . With strcpy_s you can specify the size of the destination buffer to avoid buffer overflows during copies. char tuna[5]; // a buffer which holds 5 chars incluing the null character.

What is use of strncpy () function?

The strncpy() function copies count characters of string2 to string1 . If count is less than or equal to the length of string2 , a null character (\0) is not appended to the copied string. If count is greater than the length of string2 , the string1 result is padded with null characters (\0) up to length count .


2 Answers

If the length of the string you desire to copy is unknown, you can use snprintf here. This function sends formatted output to str. It acts similarily to sprintf(), but instead does not write more bytes allocated by str. If the resulting string is longer than n-1 characters, then the remaining characters are left out. It also always includes the null terminator \0, unless the buffer size is 0.

This would be a alternative to strncpy() or strcpy(), if you really don't want to use it. However, manually adding a null terminator at the end of your string with strcpy() is always a simple, efficient approach. It is very normal in C to add a null terminator at the end of any processed string.

Here is a basic example of using sprintf():

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define SIZE 1024

int main(void) {
    const size_t N = SIZE;
    char str[N];
    const char *example = "Hello World";

    snprintf(str, sizeof(str), "%s", example);

    printf("String = %s, Length = %zu\n", str, strlen(str));

    return 0;
}

Which prints out:

String = Hello World, Length = 11

This example shows that snprintf() copied over "Hello World" into str, and also added a \0 terminator at the end.

Note: strlen() only works on null terminated strings, and will cause undefined behaviour if the string is not null terminated. snprintf() also needs more error checking, which can be found on the man page.

As others have said, this is not an efficient approach, but it is there if you go looking.

like image 186
RoadRunner Avatar answered Oct 18 '22 03:10

RoadRunner


If the behavior you want is a truncating version of strcpy that copies the longest initial prefix of the source string into a buffer of known size, there are multiple options for you:

  • You can write a tailor made function that does the job:

      char *safe_strcpy(char *dest, size_t size, char *src) {
          if (size > 0) {
              size_t i;
              for (i = 0; i < size - 1 && src[i]; i++) {
                   dest[i] = src[i];
              }
              dest[i] = '\0';
          }
          return dest;
      }
    

    Most BSD systems have a function strlcpy(char *dest, const char *src, size_t n); that operates the same. The order of its arguments is confusing as n is usually the size of the dest array, but comes after the src argument.

  • You can use strncat():

      char *safe_strcpy(char *dest, size_t size, char *src) {
          if (size > 0) {
              *dest = '\0';
              return strncat(dest, src, size - 1);
          }
          return dest;
      }
    
  • You can use snprintf() or sprintf(), but it feels like using a hydraulic press to drive in a nail:

      snprintf(dest, size, "%s", src);
    

    Alternately:

      if (size > 0) {
          sprintf(dest, "%.*s", (int)(size - 1), src);
      }
    
  • You can use strlen() and memcpy(), but this is only possible if you know that the source pointer points to a null terminated string. It is also less efficient than both of the above solutions if the source string is much longer than the destination array:

      char *safe_strcpy(char *dest, size_t size, char *src) {
          if (size > 0) {
              size_t len = strlen(src);
              if (len >= size)
                  len = size - 1;
              memcpy(dest, src, len);
              dest[len] = '\0';
          }
          return dest;
      }
    

    The inefficiency can be avoided with strnlen() if available on the target system:

      char *safe_strcpy(char *dest, size_t size, char *src) {
          if (size > 0) {
              size_t len = strnlen(src, size - 1);
              memcpy(dest, src, len);
              dest[len] = '\0';
          }
          return dest;
      }
    
  • You could use strncpy() and force null termination. This would be inefficient if the destination array is large because strncpy() also fills the rest of the destination array with null bytes if the source string is shorter. This function's semantics are very counter-intuitive, poorly understood and error-prone. Even when used correctly, occurrences of strncpy() are bugs waiting to bite, as the next programmer, bolder but less savvy, might alter the code and introduce them in an attempt to optimize the code he does not fully understand. Play it safe: avoid this function.

Another aspect of this question is the ability for the caller to detect truncation. The above implementations of safe_strcpy return the target pointer, as strcpy does, hence do not provide any information to the caller. snprintf() returns an int representing the number of characters that would have been copied if the target array was large enough, in this case, the return value is strlen(src) converted to int, which allows the caller to detect truncation and other errors.

Here is another function more appropriate for composing a string from different parts:

size_t strcpy_at(char *dest, size_t size, size_t pos, const char *src) {
    size_t len = strlen(src);
    if (pos < size) {
        size_t chunk = size - pos - 1;
        if (chunk > len)
            chunk = len;
        memcpy(dest + pos, src, chunk);
        dest[pos + chunk] = '\0';
    }
    return pos + len;
}

This function can be used in sequences without undefined behavior:

void say_hello(const char **names, size_t count) {
    char buf[BUFSIZ];
    char *p = buf;
    size_t size = sizeof buf;

    for (;;) {
        size_t pos = strcpy_at(p, size, 0, "Hello");
        for (size_t i = 0; i < count; i++) {
            pos = strcpy_at(p, size, pos, " ");
            pos = strcpy_at(p, size, pos, names[i]);
        }
        pos = strcpy_at(p, size, pos, "!");
        if (pos >= size && p == buf) {
            // allocate a larger buffer if required
            p = malloc(size = pos + 1);
            if (p != NULL)
                continue;
            p = buf;
        }
        printf("%s\n", p);
        if (p != buf)
            free(p);
        break;
    }
}

An equivalent approach for snprintf would be useful too, passing pos by address:

size_t snprintf_at(char *s, size_t n, size_t *ppos, const char *format, ...) {
    va_list arg;
    int ret;
    size_t pos = *ppos;

    if (pos < n) {
        s += pos;
        n -= pos;
    } else {
        s = NULL;
        n = 0;
    }
    va_start(arg, format);
    ret = snprintf(s, n, format, arg);
    va_end(arg);

    if (ret >= 0)
        *ppos += ret;

    return ret;
}

passing pos by address instead of by value allows for snprintf_at to return snprintf's return value, which can be -1 in case of encoding error.

like image 28
chqrlie Avatar answered Oct 18 '22 03:10

chqrlie