4
\$\开始组\$

strscpy()与标准相似strncpy()除了它总是写入一个有效的以null结尾的字符串(除非大小为零)。

因此,我有两个问题:

  1. 是我的基本实现strscpy()对的?
  2. 正在使用<错误编号h>作为负返回值的常量宏通常应该避免?
#包括<errno.h>ssize_t ft_strscpy(char*dst,const char*src,size_t size){size_t cnt;if(!大小)返回(-E2BIG);cnt=0;while(*src&&cnt<大小-1){*dst++=*src++;++碳纳米管;}*dst=“\0”;if(*src)返回(-E2BIG);收益(cnt);}

我想写我自己的strscpy()复制Linux内核中使用的.

源代码相当复杂,所以我不理解其中的大多数优化,但我注意到函数返回了-E2BIG公司扩展到-7而不是通常-1我习惯于看到错误返回。

你对此感觉如何?

当我看到这一点时,我立即考虑在我自己的代码中复制这个想法,以获得比泛型更有意义的返回-1.
但后来,我开始想,也许这不是最好的主意,可能有一些原因导致我以前在其他地方没有见过这样做。。

\$\端组\$
4
  • 1
    \$\开始组\$ 你能包括如何strcpy()不同于strscpy()? \$\端组\$
    – 哈里斯
    评论 5月22日15:40
  • \$\开始组\$ 我应该在我的原始帖子中包含这些信息吗?这是Debian手册页strscpy() 手册页.debian.org/testing/linux-manual-4.8/strscpy.9.en.html \$\端组\$ 评论 5月22日15:45
  • 2
    \$\开始组\$ 最好在帖子中添加描述,因为这是一个闻所未闻的功能。至少我以前没看过。 \$\端组\$
    – 哈里斯
    评论 5月22日15:50
  • \$\开始组\$ 你为什么要这样做?ssizet在很多方面都很笨拙,它实际上只适用于内核模式和其他没有全局错误号的地方。 \$\端组\$
    – 约书亚
    评论 5月23日20:13

3个答案

重置为默认值
\$\开始组\$

您的实现是正确的。

我不喜欢在这种情况下使用负返回值。函数试图解决的问题是什么?我认为问题的很大一部分是不正确的调用,所以我认为许多调用都会忽略返回值,而那些不忽略的调用在返回值为错误值时会神秘地出错。我更喜欢这样的设计:

char*strxcpy(char*dst,const char*src,size_t size){char*enddst=dst+大小;如果(dst==enddst)返回0;在dst上是否至少有一个字符的空间if(!(*dst=*src++))返回dst;}while(++dst<enddst);dst[-1]='\0';//空间不足-终止我们复制的内容返回0;}

通过返回指针,我们可以确保在不检查错误的情况下使用返回值很可能会立即被捕获,而不是导致一些难以发现的错误,即某些内容添加了虚假的数量。一些静态代码检查器甚至可以告诉调用方缺少空指针检查。

此外,我更喜欢用开始和结束指针来描述缓冲区,而不是用开始和长度。如您所见,这消除了需要保持数据传输系统碳纳米管在步骤中(在这个非常简单的示例中非常简单,但通常很难保证)。当你指定参数时,我计算结束数据,更改规范以便传入可能是有利的。这意味着您可以这样编写序列:

p=strxcpy(buf,“hello”,endbuf);p=strxcpy(p,“,”,endbuf);p=strxcpy(p,“世界”,endbuf);

每一步都很简单,都清楚地传递了正确的信息。注意错误返回会发生什么-我们得到一个空指针异常。错误检查意味着:

p=strxcpy(buf,“hello”,endbuf);如果(p)p=strxcpy(p,“,”,endbuf);如果(p)p=strxcpy(p,“world”,endbuf);if(!p)///句柄缓冲区太小

还是很简单。鉴于:

pos=strscpy(buf,“hello”,sizeof(buf));pos+=strscpy(buf+pos,“,”,sizeof(buf)-pos);pos+=strscpy(buf+pos,“world”,sizeof(buf)-pos);

更复杂,更难验证,因为您需要检查添加到的内容缓冲器对于第一个参数,从第三个参数的大小中减去相同的值。现在,未注意到的故障导致我们在缓冲区中后退7个字符(E2BIG是7个),这要么使我们开始重写其中的一部分,要么更糟糕的是,使我们在缓冲开始之前写入。添加错误检查更加复杂,因为我们需要保留销售时点情报系统直到测试返回值。

r=strscpy(buf,“你好”,sizeof(buf));如果(r>=0){pos=r;r=strscpy(buf+pos,“,”,sizeof(buf)-pos);}如果(r>=0){位置+=r;r=strscpy(buf+pos,“world”,sizeof(buf)-pos);}如果(r>=0)pos+=r;if(r<0)//句柄错误

有些人可能更喜欢使用嵌套如果向右移动的语句。

如果缓冲器是指针而不是数组,差异甚至更大:

r=strscpy(buf,“hello”,buflen);buf+=r;buflen-=r;r=strscpy(buf,“,”,buflen);buf+=r buflen-=r;r+=strscpy(buf,“world”,buflen);buf+=r;buflen-=r;

在其中添加错误检查是留给读者的练习。

\$\端组\$
2
  • \$\开始组\$ 因为我接着解释了为什么我认为不应该传入长度,所以它使这个版本尽可能接近将enddst作为参数的版本。如果您认为它总是返回空指针,我建议您尝试用一个简单的测试用例编译它。 \$\端组\$
    – 第19页
    评论 6月8日23:35
  • \$\开始组\$ 是的,我错过了if(!(*dst=*src++))返回dst;,因此代码并不总是返回无效的。注释已删除。 \$\端组\$ 评论 6月9日0:32
7
\$\开始组\$

使用C99的新含义静止的关键词:

如果我们改变参数,我们可以:

ssize_t ft_strscpy(大小_大小,char dst[限制静态大小],const char src[限制静态大小]);

现在如果我们这样称呼它:

int main(无效){字符buf[10]={};ft_strscpy(10,buf,“feo”);}

编译器很好地输出了一些诊断信息:

c.c:在函数“main”中:c.c:26:5:警告:“ft_strscpy”正在从大小为4的区域读取5个字节[-Wstringop-overread]26|ft_strscpy(5,buf,“feo”);|     ^~~~~~~~~~~~~~~~~~~~~~~~~c.c:26:5:注意:引用“const char[]”类型的参数3抄送:3:9:注意:在调用函数'ft_strscpy'时3|ssize_t ft_strscpy(size_t大小,|         ^~~~~~~~~~

它也更具有自我记录功能,并且限制可能允许编译器更好地优化函数,因为它可以假定两个指针没有别名。

缺少单元测试:

我们可以测试一些东西:

  • 函数是否正确返回复制的字节数?
  • 功能是否返回-E2BIG公司如果目标缓冲区不够大?
  • 函数返回后,两个字符串比较是否相等?

您不需要为此提供测试库/框架。使用资产()<断言.h>.


我注意到函数返回了-E2BIG,它扩展到-7而不是通常的-1,我习惯于看到错误返回。

你对此有何感想?

如果我试图实施,我的感受是无关紧要的strscpy(9)符合规范要求在这里.

如果你不喜欢回来-E2BIG公司,您可以返回其他内容,但它将不再是strscpy(),指定返回-E2BIG公司,但应在文档中指定修改后的版本。

请参见:为什么返回负错误号?(例如退货-EIO).

噪音:

返回不需要任何括号。更改此选项:

返回(-E2BIG);...收益(cnt);

收件人:

返回-E2BIG;...返回cnt;

使用有意义的标识符:

碳纳米管不如计数至少它读起来和听起来都不像粗俗的俚语。

在声明点初始化变量:

size_t cnt;if(!大小)返回(-E2BIG);cnt=0;

没有分配的理由cnt=0离它的宣言还很远。考虑:

size_t cnt=0;

这就是我要实现的ft_strscpy():

#包括<errno.h>#包括<string.h>ssize_t ft_strscpy(大小_大小,char dst[限制静态大小],const char src[限制静态大小]){如果(大小==0){返回-E2BIG;}void*const res=内存副本(dst,src,'\0',大小);返回res!=空指针?(res-dst-1):(dst[size-1]='\0',-E2BIG);}

我已经改变了参数。如果您希望遵守规范,那么应该使用与代码中相同的原型。虽然其他人可能会对逗号操作符的使用感到不悦,但我觉得这里没问题。

有关的详细信息内存复制(),请参阅:memccpy(3)-Linux手册页。它是C23的一部分。

如果你问我,它呈现strncpy(),strlcpy(),strscpy(),stpcpy(),strcpy_s(),字符串_()和其他变体几乎毫无用处。至少strncpy()现在终于可以死了。

\$\端组\$
18
  • 1
    \$\开始组\$ 我没有DV。但由于参数顺序已经在原始定义中指定,所以我认为不适合四处移动参数。 \$\端组\$
    – 巴尔马
    评论 5月23日14:45
  • 1
    \$\开始组\$ AIUI、,strncpy()是为原始Unix文件系统创建的,该文件系统有一个14个字符的文件名字段,并允许最大大小的文件名不以null结尾。AFAIK文件系统已经过时很久了,所以我们确实不需要strncpy()再。 \$\端组\$
    – 巴尔马
    评论 5月23日14:55
  • 1
    \$\开始组\$ @Harith啊,好吧。MSVC确实支持这一点,通过SAL公司。 \$\端组\$ 评论 6月6日4:23
  • 1
    \$\开始组\$ @Davislor我不是说我们不需要strcpy()有个限制,我是说应该是这样的strscpy(),不像strncpy(),因此结果总是有一个空终止符。 \$\端组\$
    – 巴尔马
    评论 6月6日15:05
  • 1
    \$\开始组\$ @Davislor Right,C通常设计为只处理以null结尾的字符串。正如我所说,这是为一种特定的情况创建的,在这种情况下,null终止符是可选的,以优化磁盘空间,因此它们不必为最大长度的文件名浪费一个字节。有一段时间内存和磁盘空间都很昂贵,每个字节都很重要。 \$\端组\$
    – 巴尔马
    评论 6月7日14:42
4
\$\开始组\$

意见:论证顺序

ssize_t ft_strscpy(char*dst,const char*src,size_t size)即使匹配sized_strscpy(),分隔开数据传输系统大小。我想最常见的用法应该是:

char dst[100];const char src=。。。;ssize_t retval=ft_strscpy(dst,src,dst大小);

然而

ssize_t retval=ft_strscpy(dst,dst大小,src);//或ssize_t retval=ft_strscpy(dst、dst、src的大小);

保持指向其目标和大小的指针作为相邻参数。

此外,使用

ssize_t retval=ft_strscpy(dst、dst、src的大小);

我们可以编写以下代码,有点像@哈里斯回答得很好。

ssize_t ft_strscpy(大小_大小,char dst[限制静态大小],const char src[restrict 1]);
\$\端组\$

你的答案

单击“发布您的答案”,表示您同意我们的服务条款并确认您已阅读我们的隐私政策.

不是你想要的答案吗?浏览标记的其他问题问你自己的问题.