我的strlcpy版本

时间:2010-05-29 03:58:05

标签: c

gcc 4.4.4 c89

我的程序做了很多字符串处理。我不想使用strncpy,因为它没有终止。我不能使用strlcpy作为不可移植的。

只是几个问题。我怎样才能完成我的功能以确保它完全安全和稳定。单元测试?

这对生产来说是否足够好?

size_t s_strlcpy(char *dest, const char *src, const size_t len)
{
    size_t i = 0;

    /* Always copy 1 less then the destination to make room for the nul */
    for(i = 0; i < len - 1; i++)
    {
        /* only copy up to the first nul is reached */
        if(*src != '\0') {
            *dest++ = *src++;
        }
        else {
            break;
        }
    }

    /* nul terminate the string */
    *dest = '\0';

    /* Return the number of bytes copied */
    return i;
}

非常感谢任何建议,

10 个答案:

答案 0 :(得分:11)

虽然你可以像其他帖子推荐的那样简单地使用另一个strlcpy函数,或者使用snprintf(dest, len, "%s", src)(它总是终止缓冲区),这是我注意到的代码:

size_t s_strlcpy(char *dest, const char *src, const size_t len)
{
    size_t i = 0;

不需要在这里制作len const,但它可以提供帮助,因为它会检查以确保您没有修改它。

    /* Always copy 1 less then the destination to make room for the nul */
    for(i = 0; i < len - 1; i++)
    {

糟糕。如果len为0怎么办? size_t通常是无符号的,因此(size_t)0 - 1最终会变成类似4294967295的内容,导致您的例程通过程序的内存进行处理并崩溃到未映射的页面。

        /* only copy up to the first nul is reached */
        if(*src != '\0') {
            *dest++ = *src++;
        }
        else {
            break;
        }
    }

    /* nul terminate the string */
    *dest = '\0';

上面的代码对我来说很好。

    /* Return the number of bytes copied */
    return i;
}

根据Wikipediastrlcpy返回strlen(src)(字符串的实际长度),而不是复制的字节数。因此,您需要继续对src中的字符进行计数,直到您点击'\0',即使它超过len

此外,如果你的for循环终止于len - 1条件,你的函数将返回len-1,而不是像你期望的那样。


当我写这样的函数时,我通常更喜欢使用开始指针(称之为S)和结束指针(称之为E)。 S指向第一个字符,而E指向最后一个字符后的一个字符(这使得E-S是字符串的长度)。虽然这种技术可能看起来很丑陋而且模糊不清,但我发现它相当健壮。

以下是我将如何编写strlcpy的过度评论版本:

size_t s_strlcpy(char *dest, const char *src, size_t len)
{
    char *d = dest;
    char *e = dest + len; /* end of destination buffer */
    const char *s = src;

    /* Insert characters into the destination buffer
       until we reach the end of the source string
       or the end of the destination buffer, whichever
       comes first. */
    while (*s != '\0' && d < e)
        *d++ = *s++;

    /* Terminate the destination buffer, being wary of the fact
       that len might be zero. */
    if (d < e)        // If the destination buffer still has room.
        *d = 0;
    else if (len > 0) // We ran out of room, so zero out the last char
                      // (if the destination buffer has any items at all).
        d[-1] = 0;

    /* Advance to the end of the source string. */
    while (*s != '\0')
        s++;

    /* Return the number of characters
       between *src and *s,
       including *src but not including *s . 
       This is the length of the source string. */
    return s - src;
}

答案 1 :(得分:4)

恕我直言,只需按下original strlcpy,Ignacio Vazquez-Abram简洁地说道。 OpenBSDs代码是战斗的,许可条款摇滚;)。

至于你的代码,我会添加到其他人已经说过的内容,只是个人品味的问题:

/* only copy up to the first nul is reached */
if(*src != '\0') {
    *dest++ = *src++;
}
else {
    break;
}

我会写这样的:

if(*src == '\0') {
    break;
}
*dest++ = *src++;

两者都是因为它减少了人们需要阅读的不必要代码的数量,并且因为这是我的“风格”,而不是if (ok) { do } else { handle error }。 if上方的注释也是多余的(请参阅for循环上方的注释)。

答案 2 :(得分:3)

为什么不使用memccpy()之类的东西而不是自己滚动?您只需要使用空字节终止,但是增加标准函数比从头开始更容易且通常更快。

某些体系结构会对字符串函数进行大量优化甚至使用汇编来从中挤出良好的性能。

无需构建或调试:

str = memccpy (dest, src, '\0', len);
if(str)
    *str = '\0';

答案 3 :(得分:2)

我建议White-box testing对于像这样的函数(单元测试的一种形式)很有用。

答案 4 :(得分:2)

DRY原则是“不要重复自己”。换句话说,不要创建新代码来执行已完成交易的事情 - 检查标准C库,如上例(WhilrWind)所示。

一个原因是提到的测试。标准的C库已经过多年的测试,所以它可以像宣传的那样安全地工作。

通过玩代码学习是一个好主意,继续尝试。

答案 5 :(得分:1)

是的,单元测试。检查大量随机生成的字符串。

但对我来说很好看。

答案 6 :(得分:1)

答案 7 :(得分:1)

我认为如此依赖长度并对其进行算术是错误的。

size_t类型未签名。考虑如果使用0大小的目标调用函数将如何表现。

答案 8 :(得分:1)

  

单元测试?   这对生产来说还不错吗?

可能对于像这样的“简单”函数来说它可能就足够了,尽管测试函数的唯一真正方法是试图破解它。

传递给它NULL指针,10k字符长字符串,len的负值,某种程度上损坏的数据,等等。一般认为:如果你是一个试图打破它的恶意用户,你会怎么做?

请参阅我的回复here

中的链接

答案 9 :(得分:1)

嗯,没有意识到这是一个老帖子。

这样够生产吗?
完全安全和稳定(?)

弱点:
无法正确处理len == 0-易于修复。
当源很长时,返回值值得怀疑-易于修复。
(尚未讨论)不考虑重叠的dest, src

使用if(*src != '\0') { *dest++ = *src++; }覆盖 null chracter 在读取之前覆盖它很容易产生意想不到的结果,因此迭代的风险超越了原始'\0'

// pathological example                 
char buf[16] = "abc";
const char *src = buf;       // "abc"
const char *dest = buf + 2;  // "c"
size_t dest_sz = sizeof buf - 2;
s_strlcpy(dest, src, dest_sz);
puts(dest); // "ababababababa", usual expectation "abc"

提出两种解决方案:

restrict
从C99开始,C具有restrict,它向编译器指示它可以假定通过src读取和通过dest写入的数据不会重叠。这使编译器可以使用某些其不能使用的优化。 restrict还通知用户不要提供重叠缓冲区。

  • 代码仍然可以如上所述失败,但这就是调用者违反了合同,而不是s_strlcpy()

注意:const中的const size_t len是对函数声明的干扰。与size_t size相比,使用size_t len也更清楚。

size_t s_strlcpy(char * restrict dest, const char * restrict src, size_t size);

restrict用法类似于标准库strcpy()和其他库。

char *strcpy(char * restrict s1, const char * restrict s2);

句柄重叠
另一种方法是使s_strlcpy()能够容忍重叠内存,如下所示。这几乎意味着代码需要使用memmove()

size_t s_strlcpy(char *dest, const char *src, const size_t dest_size) {
  size_t src_len = strlen(src);
  if (src_len < dest_size) {
    memmove(dest, src, src_len + 1);  // handles overlap without UB
  } else if (dest_size > 0) {
    // Not enough room
    memmove(dest, src, dest_size - 1);  // handles overlap without UB
    dest[dest_size - 1] = '\0';
  }
  return src_len;  // I do not think OP's return value is correct. S/B src length.
}

希望我已经正确编码了strlcpy()的所有功能。边缘情况需要花费一些时间来解决。