我的strlcpy版本

问题描述 投票:4回答:9

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;
}

非常感谢任何建议,

c
9个回答
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;
}

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循环上方的注释)。


3
投票

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

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

没有构建或调试:

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

2
投票

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


2
投票

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

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

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


1
投票

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

不过对我来说很好看。


1
投票

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

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


1
投票

单元测试?这对生产来说足够好吗?

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

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

请参阅我的回复here中的链接


© www.soinside.com 2019 - 2024. All rights reserved.