对strlcat的一点探讨

http://www.gratisoft.us/todd/papers/strlcpy.html

该链接对两个函数的说明如下:

The strlcpy() and strlcat() functions return the total length of the string they tried to create. For strlcpy() that is simply the length of the source; for strlcat() that means the length of the destination (before concatenation) plus thelength of the source. To checkfor truncation, the programmer need only verify that the return value is less than the size parameter. Thus, if truncation has occurred, the number of bytes needed to store the entire string is now known and the programmermay allocate more space and re-copy thestrings if he or she wishes. The return value has similar semantics to the returnvalue of snprintf() as implemented in BSD and as specified by the upcoming C9Xspecification [4] (note thatnot all snprintf() implementations currently comply with C9X). If no truncation occurred, the programmer now has the lengthof the resulting string. This isuseful since it is common practice to build a string with strncpy() andstrncat() and then to find the length of the result using strlen(). Withstrlcpy() and strlcat() the final strlen() is no longer necessary.

也就是说:

1.                  strlcpy 和 strlcat 函数返回最终创建的字符串长度。对于 strlcpy 来说是源字符串的长度,对于 strlcat 来说意味着目标的长度加源的长度。

2.                  为了检查截断,程序员只需验证返回值是否小于 size 参数。因此,如果发生截断,那么根据返回值可以知道总共需要多大的空间才足以放下目标串和源串,此时如果程序员愿意,可以重新分配空间,并重新复制这些字符串。

3.                  如果没有截断发生,程序员现在通过返回值获得了最终字符串的总长度;这是有用的,因为通常用 strncpy 和strncat 来构造字符串然后使用 strlen 来取得字符串的长度。使用 strlcpy 和 strlcat,原来作为后续操作的strlen 就不需要了。

以下链接给出了整篇文章的翻译:

http://kapok.blog.51cto.com/517862/112454

翻译得不是很贴切,其中提到“如果发生截断,可以发现已经存储了多少个字节”,原文并未出现类似的说法。阅读原码可以知道,仅仅通过strlcpy和strlcat本身是无法获得已截断的字节数的。

另外一个链接:

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0349bc/CJABDHJJ.html

提到“可以调用一次 strlcat() 以了解需要多少空间,然后分配空间(如果没有足够的空间),最后再次调用 strlcat() 以创建所需的字符串”,经过验证,这种连续调用两次的做法是错误的,会带来源串被第一次strlcat调用截断部分的重复复制,从而导致源串后面等长子串的丢失。

首先看一下strlcat的源码实现:

http://sources.redhat.com/bugzilla/attachment.cgi?id=91

http://www.cppblog.com/tx7do/archive/2009/10/08/98071.html

下面给出源码的运行示例:dst→目标字符串,src1→源字符串1,src2→源字符串2,现在先把src1追加到dst,接着再追加src2,下面给出了整个过程中,dst字符串的变化:



可以看到,追加src1时,dst拥有足够的空间,因而追加操作得以正确执行,并且函数返回ret1=dlen+s1-src1,即目标串长+源串长。继续追加src2,dst尚有空闲空间,然而不足以放下整个src2串,这时候就发生字符串截断了。也就是dst余下空闲空间(假设长r)正常复制了src2的前r个字符,src2余下内容则被丢失。这种情况下,如果简简单单地追加空间,然后再调用一次strlcat,显然得不到正确的结果,因为被截断的那r个字符被重复复制了。

验证代码如下:

//strlcpy.cpp

#include<stdlib.h>

size_t strlcpy( char *dst, const char *src, size_t siz )

{

    char*            d = dst;

    const char*        s = src;

    size_t            n = siz;

 

    if (s == 0 || d== 0) return 0;

 

    /* Copy as manybytes as will fit */

    if (n != 0&& --n != 0)

    {

        do

        {

            if ((*d++= *s++) == 0)

               break;

        } while (--n!= 0);

    }

 

    /* Not enoughroom in dst, add NUL and traverse rest of src */

    if (n == 0)

    {

        if (siz != 0)

            *d ='\0';                /* NUL-terminate dst*/

        while (*s++)

            ;

    }

 

    return(s - src -1);        /* count does not include NUL*/

}

 

//strlcat.cpp

#include<stdlib.h>

#include<string.h>

 

size_t strlcat( char* dst, const char* src, size_t siz )

{

    char*        d = dst;

    const char*    s = src;

    size_t        n = siz;

    size_t        dlen;

 

    if (s == 0 || d== 0) return 0;

 

    while (n-- != 0&& *d != '\0')

    {

        d++;

    }

    dlen = d - dst;

    n = siz - dlen;

 

    if (n == 0)

    {

        return(dlen +strlen(s));

    }

    while (*s !='\0')

    {

        if (n != 1)

        {

            *d++ =*s;

            n--;

        }

        s++;

    }

    *d = '\0';

 

    return(dlen + (s - src));

}

 

#include<stdio.h>

#include<stdlib.h>

#include<string.h>

 

extern size_t strlcat( char* dst, const char* src, size_tsiz );

extern size_t strlcpy( char *dst, const char *src, size_tsiz );

 

int main(void)

{

    char* a=(char*)malloc(sizeof(char)*32);

    char* b;

    int len;

    len=strlcpy( a,"Hello I am Levin, yeah! ", 32);

    if(len>=32)

    {

       printf("Not enough space!\n");

        return -1;

    }

    len=strlcat( a,"Computer science is very interesting!", 32);

    printf("len:%d\n",len);

   printf("%s\n",a);

    if(len>=32)

    {

       b=(char*)realloc(a,len);

    }

    else

    {

        b=a;

    }

   printf("strlen(b): %d\n",strlen(b));

   strlcat(b,"Computer science is very interesting!", len);

    printf("%s\nShouldbe:Hello I am Levin, yeah! Computer science is very interesting!\n",b);

    free(b);

    return 0;

}

 

执行结果如下:


可以看到“Compute”被重复复制了,两次调用strlcat的做法得不到正确的结果“Hello I am Levin, yeah! Computer science is veryinteresting!”。

接下来考虑的是,为什么strlcat的实现中当目标串空闲空间不足以放下整个源串的时候,还要进行截断复制呢?这种操作有意义么?能不能当这种情况出现的时候不予复制而直接返回目标串长+源串长作为出错依据?

尝试修改strlcat如下:

//strlcat.cpp

#include<stdlib.h>

#include<string.h>

 

size_t strlcat( char* dst, const char* src, size_t siz )

{

    char*        d = dst;

    const char*    s = src;

    size_t        n= siz;

    size_t        dlen;

 

    if (s == 0 || d== 0) return 0;

 

    while (n-- != 0&& *d != '\0')

    {

        d++;

    }

    dlen = d - dst;

    n = siz - dlen;

 

#if 1

    int m=n;

    char* p=d;

#endif

 

    if (n == 0)

    {

        return(dlen +strlen(s));

    }

 

    while (*s !='\0')

    {

        if (n != 1)

        {

            *d++ =*s;

            n--;

        }

        s++;

    }

 

#if 1

    if(m <(s-src))

        *p = '\0';

#endif

 

    *d = '\0';

 

    return(dlen + (s- src));

}

      即增加了以下两个代码段:


……

 

        关闭条件编译即可还原成原来的实现,这种改动虽然不能避免截断的发生,但是在截断发生的时候可以将截断部分的起始位置(p指针的作用)强制设为结束符‘\0’。这样在重新开辟空间后可以满足正确的追加操作,“连续”两次调用strlcat此时成了真正可行的做法了。如下所示:


       以上是阅读strlcat的一点想法,欢迎交流指正。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值