strncat和strncpy写安全字符串复制函数有什么区别吗?

问题描述 投票:0回答:3

我遇到了两种流行的方法来编写可移植且符合 C89 的安全复制功能。

例一:

strncpy(dst, src, size);
dst[size - 1] = '\0';

例二:

dst[0] = '\0'
strncat(dst, src, size - 1);

这些方法是否提供完全相同的结果,或者一个示例与另一个示例之间的

dst
内容是否有任何差异?

c string c89
3个回答
4
投票

是的,它们在技术上是不同的。虽然你可能不关心这些微不足道的差异。

例如如果你这样初始化:

char dst[] = "abcdefg";
char src[] = "12";
size_t size = sizeof dst;

然后用你的“例子1”,

dst
变成
0x31 0x32 0x00 0x00 0x00 0x00 0x00 0x00

有了你的“例子2”,

dst
变成了
0x31 0x32 0x00 0x64 0x65 0x66 0x67 0x00

如果你只想复制字符串,那么区别就无所谓了。

很难说哪个更好。但是对于一个非常大的

size
和一个非常短的
src
的情况,使用“示例1”的方法设置所有尾随空字符可能会使程序变慢一些。


0
投票

如果源字符串比目标数组长,则两个代码片段都可以执行截断的字符串复制操作,包括空终止符,并且目标数组中的结果字符串将被正确地以空终止。

但是请注意这些评论:

  • strncpy
    还将用空字节填充目标数组,直到完整的
    size
    字节。这通常是不必要和浪费的。

  • 第一个代码片段设置了两次

    dst[size - 1]
    。你可以改写:

      strncpy(dst, src, size - 1);
      dst[size - 1] = '\0';
    
  • 大多数 C 程序员对

    strncpy
    的语义知之甚少,这使它变得混乱和容易出错。即使在解决问题的情况下,也建议避免使用此功能。

  • 第二个代码片段,使用

    strncat
    对于大型目标数组来说浪费较少,但仍然不是最优的。

  • 两种方法都会失败并且具有未定义的行为是

    size
    0
    .

这些方法是否提供完全相同的结果,或者一个示例与另一个示例之间的

dst
内容是否有任何差异?

不,不完全是,如上所述

哪个更好:

第二个,但更好的解决方案是编写一个自定义函数,它可以正确处理所有情况,不执行冗余复制或填充,并返回一个指示器供调用者检测截断。

这里是一个简单的实现:

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

这样使用:

void test_function(const char *str) {
    char buf[200];

    // copy with truncation
    safe_strcpy(buf, sizeof buf, str);

    // copy with truncation and test
    if (safe_strcpy(buf, sizeof buf, str) < sizeof buf) {
        // no truncation
    } else {
        // truncation occurred
    }

    // safe concatenation:
    size_t pos = 0;
    pos += safe_strcpy(buf + pos, sizeof(buf) - pos, str);
    pos += safe_strcpy(buf + pos, sizeof(buf) - pos, str);
    pos += safe_strcpy(buf + pos, sizeof(buf) - pos, str);
    pos += safe_strcpy(buf + pos, sizeof(buf) - pos, str);
    if (pos < sizeof buf) {
        // str was replicated 4 times successfully
    } else {
        // truncation occurred, strlen(buf) is 199
    }
}

-2
投票

https://en.cppreference.com/w/cpp/string/byte/strncat
https://en.cppreference.com/w/cpp/string/byte/strncpy

这里有一个C++(哈哈哈)的例子,说明了区别:
https://wandbox.org/permlink/igKe0CtdyuMw1JHL

#include <iostream>
#include <cstring>
#include <cassert>

//////////////////////////////////////////////////////////
// using strncat
// https://en.cppreference.com/w/cpp/string/byte/strncat

template <std::size_t N>
inline void safestrcpy1(char *dest, const char *src)
{
    constexpr std::size_t count = N-1;
    dest[0] = '\0';
    strncat(dest, src, count); // at most count characters are copied from string src; 
                               // and then a terminating null character '\0' is written if it was not yet encountered
}

template <std::size_t N>
inline void safestrcpy1(char (&dest)[N], const char *src)
{
    safestrcpy1<N>(&dest[0], src);
}

//////////////////////////////////////////////////////////
// using strncpy
// https://en.cppreference.com/w/cpp/string/byte/strncpy

template <std::size_t N>
inline void safestrcpy2(char *dest, const char *src)
{
    constexpr std::size_t count = N-1;
    strncpy(dest, src, count); // null termination '\0' is only written if it occurs somewhere within src[0] .. src[count-1]
                               // if it occurs at src[i] and i < count, then dest[i+1], dest[i+2], ... dest[count-1] are also set to '\0'.
    dest[count] = '\0';        // ensure null termination
}

template <std::size_t N>
inline void safestrcpy2(char (&dest)[N], const char *src)
{
    safestrcpy2<N>(&dest[0], src);
}


//////////////////////////////////////////////////////////
// main
//
int main()
{
    {
        char dest1[4] = "xyz";
        char dest2[4] = "xyz";
        safestrcpy1(dest1, "");
        safestrcpy2(dest2, "");
#define RES1A "\0yz"
#define RES2A "\0\0\0"
        assert(memcmp(dest1, RES1A, sizeof(RES1A)) == 0);
        assert(memcmp(dest2, RES2A, sizeof(RES2A)) == 0);
        std::cout << dest1 << std::endl;
    }
    {
        char dest1[4] = "xyz";
        char dest2[4] = "xyz";
        safestrcpy1(dest1, "1");
        safestrcpy2(dest2, "1");
#define RES1B "1\0z"
#define RES2B "1\0\0"
        assert(memcmp(dest1, RES1B, sizeof(RES1B)) == 0);
        assert(memcmp(dest2, RES2B, sizeof(RES2B)) == 0);
        std::cout << dest1 << std::endl;
    }
    {
        char dest1[4] = "xyz";
        char dest2[4] = "xyz";
        safestrcpy1(dest1, "12");
        safestrcpy2(dest2, "12");
#define RES1C "12\0"
#define RES2C "12\0"
        assert(memcmp(dest1, RES1C, sizeof(RES1C)) == 0);
        assert(memcmp(dest2, RES2C, sizeof(RES2C)) == 0);
        std::cout << dest1 << std::endl;
    }
    {
        char dest1[4] = "xyz";
        char dest2[4] = "xyz";
        safestrcpy1(dest1, "123");
        safestrcpy2(dest2, "123");
#define RES1D "123"
#define RES2D "123"
        assert(memcmp(dest1, RES1D, sizeof(RES1D)) == 0);
        assert(memcmp(dest2, RES2D, sizeof(RES2D)) == 0);
        std::cout << dest1 << std::endl;
    }
    {
        char dest1[4] = "xyz";
        char dest2[4] = "xyz";
        safestrcpy1(dest1, "1234");
        safestrcpy2(dest2, "1234");
#define RES1E "123"
#define RES2E "123"
        assert(memcmp(dest1, RES1E, sizeof(RES1E)) == 0);
        assert(memcmp(dest2, RES2E, sizeof(RES2E)) == 0);
        std::cout << dest1 << std::endl;
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.