设计一个更好的strcpy

2021-06-20 02:47:15

与它们一样,终止终止字符串对于C至关重要,并且在所有琐碎的程序中都必须与它们一起工作。虽然C型字符串是使用语言的基本部分,但操作它们是安全错误和性能丢失的共同来源。其中一个最常见的操作是将字符串从一个缓冲区复制到另一个,并且有各种字符串函数声称在C.轶事中执行此操作,但是,对他们实际做的事情有很大的困惑,而且许多人渴望具有以下属性的字符串复制功能:

该函数应接受一个返回的源字符串,目标缓冲区和表示目标缓冲区大小的整数。

返回后,函数应确保目的地缓冲区在可能时(具体地,当目的地缓冲器具有非零大小时)以避免未经未勿绒的字符串的问题,以确保目标缓冲区指向包含源极字符串的前缀。 (虽然字符串截断有自己的问题,但通常是一个相当合理的回力。)

该函数应指示它从源复制的字符数量,以及指示是否发生了溢出。 (如果需要,这允许处理溢出。)

该函数应该是有效的,它不应该读取或写入它不必的内存。这些部分携手合作:函数应该在单个通行证中运行,而不是将目标缓冲区写入NUL字节IT位置,或者从源字符串中读取字符,一旦确定它已填充目标缓冲区。理想情况下,实现将是矢量图(放宽一些先前的约束,以便在平台对齐保证中)。

该功能应该是标准化的,因此可以跨系统使用它。符合ISO C或POSIX.1通常是最理想的。

也就是说,通常需要的是下面的功能,我们将调用strxcpy:

char * strxcpy(char *限制dst,const char *限制src,size_t len){if(!len){return null; } whiled( - len&&(* dst ++ = * src ++));如果(!len){* dst ++ =' \ 0' ;返回* src? null:dst; }否则{返回dst; }}

除了标准化之外,此函数将从SRC到DST和CUP副本复制STRLEN(SRC)或LEN - 1字节的较小函数。在SRC在DST中配合的情况下,它将返回一个指针,它放置了NUL字节;否则,它返回null以指示截断。虽然目前的编译器似乎在其控制流程时遇到困难,但它也应该相当直接向矢量化,因为核心环路有点类似于Strncpy和Strlen的组合。

通过指导回顾,让我们来看看各种复制例程,看看它们是否可以帮助我们。

要远离通常的问题,我们将假设我们必须使用C,并且我们将避免使用作为第三方库可用的各种长度前缀或聚合字符串结构。除了使用不同的语言时可以解决此处提到的语言中的许多问题;它并不总是可取的甚至可能利用它们。除了使用第三方库的通常缺点外,替换空终止字符串通常会导致添加句法开销和不兼容性,这些代码旨在与它们一起使用。

标准strcpy函数,将字符从src副本复制到dst,最多,包括第一个NUL字节。如果DST小于或别名SRC,则程序的行为是未定义的。 DST被归还。

Strcpy肯定会满足要求2和4的部分:它将始终写出空终止的字符串,它会很快。但是,它根本无法执行界限,所以我们只能使用它,如果我们知道我们的目标缓冲区小于我们的源缓冲区 - 它失败要求1.此外,它并不告诉我们它写了多少个字符,即它要求3.这是C的一部分,所以它确实满足要求5。

STRNCPY将从SRC到DST的LEN字符复制到LEN字符。如果SRC短于LEN,则DST为NUL-PADDED到LEN字符。 DST被归还。

Strncpy采用我们想要的参数,所以它满足要求1;即使在一个任意源字符串的面上,它也不会表现出未定义的行为,只要我们将其提供正确的目的地缓冲区长度。但是,如果源更长的是目标,则缓冲区不会终止,如果它缩短strncpy将继续将NUL字节写入目的地。此外,它并不指示写入源中的许多字符,但是可以通过将NUL字节写入目标缓冲区的最后一个字符并在呼叫后检查来检测溢出。这意味着它失败了2,3和4的要求,但由于它已经存在,只要它确实满足要求5。

Memcpy根本不关心NUL角色;它甚至不需要源是空终止的字符串。它失败了蝙蝠的前三个要求,但它是C的一部分,它肯定会达到要求4和5。

strcpy_s符合ISO C11,如果在包括字符串之前定义__stdc_want_lib_ext1__并且定义__stdc_lib_ext1__,则可用。

执行相同操作的strcpy的界限版本,除了它可以将未指定的值写入DST的剩余部分,如果src == null,dst == null,如果发生截断,则Len为零或大于RSIZE_MAX或SRC和DST重叠,如果可能,它将将NUL字节写入* DST,返回非零值,并调用约束处理程序函数。

在表面上,这种功能似乎有用 - 但仔细观察显示它有许多不幸的问题。最大的是,任何截断都会调用约束处理程序功能,该函数可以执行许多事情,例如中止程序。此外,它并没有告诉我们它写了多少,可以涂鸦目的地,并且是标准化的,但仅作为C11的可选扩展名。总的来说,它只满足要求1。

strncpy_s符合ISO C11,如果在包括字符串之前定义__stdc_want_lib_ext1__和定义__stdc_lib_ext1__,则可用。

strncpy的界限版本,如果src == null,dst == null,则返回非零值,如果发生截断,则Len为零或大于RSize_max,或SRC和DST重叠,在这种情况下将NUL字节写入* DST,如果可能,未指定的值为DST的剩余部分,返回非零值,并调用约束处理程序函数。否则,将从SRC复制LEN字节从SRC到DST,然后在DST [LEN-1]中添加NUL终端字节,返回零。

此函数具有与strcpy_s相同的约束处理程序问题,也是标准化但通常不可用。虽然当字符串适合并仅在错误时删除目的地时,它将终止,但它仍然只满足第一个要求。

与strncpy相同,除了返回指向写入NUL字节的指针,如果有的话;否则返回DST + LEN。

stpncpy是对strncpy的改进,但它只修复了检测终止或溢出的问题,这是要求3.它仍然失败的要求2,因为它不一定是空终止,它失败要求4因为它将nuls写入结束目的地缓冲区。与它是POSIX的一部分,但除了要求1之外,它仍然符合要求5。

当与%s一起使用时,将第一个Variadic参数(一个字符串)复制到DST,或者第一LEN - 1字节后跟NUL字节。返回第一个Variadic参数的长度。

SnPrintf是一种奇怪的包含,但它是一个标准函数,可以帮助我们,如果我们使用“%s”作为格式字符串,则占据尺寸和终止其目的地。它符合要求1,2和5,但在3和4下降:其返回值基本上是“Sprintf将返回的是什么”,这意味着它必须至少执行相同的Strlen。这很慢,而不是我们想要的,以及一个int(不是size_t)。

语义上等同于Sprintf(DST,Len,"%s" src)保存返回值,这是一个size_t。

strlcpy与之前的Sprintf调用相同,除了它使用正确的size_t return类型。这仍然意味着它无法满足3和4的性能要求,而不是标准,因此它不满足5。由于它执行副本并将您带有零终止的字符串,它填充了前两个要求。

strscpy复制src到dst如果它适合缓冲区并返回复制的字符数,则不包括尾随NUL字节;否则它将复制第一个LEN - 1个字符并将DST [LEN-1]设置为NUL字节,返回-e2big。

strscpy是我们看到的第一个功能,它满足了四个功能要求:它可以追溯到尽可能大的源字符串,null终止目标缓冲区,返回复制的字符数,并且不执行过多的读取或写入。实际上,我们可以使用它来实现我们的strxcpy函数:

char * strxcpy(char *限制dst,const char *限制src,size_t len){ssize_t复制= strscpy(dst,src,len);复制返回!= - e2big:src +复制+ 1:null; }

它有两个问题:第一个问题,它是它返回一个ssize_t而不是size_t,但实际上这不是一个问题。第二个是,不幸的是非标准 - 这是Linux内核为自己写的东西 - 这意味着它违反了要求5。

Memccpy与Memcpy相同,但如果SRC在DST中的位置后,SRC包含CHR,复制CHR和返回指针,则可能会过早停止。如果在不遇到CHR的情况下复制LEN字符,则返回NULL。

Memccpy,当与NUL字符一起使用时,满足除了第二个之外的所有要求,但这是微不足道的修复:

char * strxcpy(char *限制dst,const char *限制src,size_t len){char * end = memccpy(dst,src,' \ 0',len);如果(!结束){dst [len-1] =' \ 0' ;返回结束; }

虽然它将在即将推出的C标准中发货,但它已经广泛使用作为流行的可选POSIX扩展。

对于简洁起见,存在一些其他功能 - Stpcpy,Mempcpy,Sprintf,Sprintf_s和SnPrintf_s - 因为它们的行为(和问题)是基于其他功能的相当自我解释。 (Mempcpy是一个GNU扩展。)

复制C字符串在C中是一个极其常见的操作,但是如此安全有效地进行了非琐碎。几乎所有目前可用的字符串例程,标准化与否,具有微妙的怪癖,通常阻止它们匹配达到它们的程序员的期望。这个问题是通过许多样式指南或林特将建议使用其中一个(或有时超过一个!)这些函数来替换Strcpy而不讨论其限制。最后,正如我们在上面所看到的那样,职能相当差:我们的StrxCPY,学术而不是不可能的场景,无法在其实施中使用任何一个;人们只能想象那些为其编写ad-hoc的人来说,这两个错误都可能产生错误。

相比之下,Memccpy的标准化是一个非常欢迎的改进,因为它促进了更安全和更高效的字符串算法的构建 - 除了Strxcpy之外,讨论的许多功能也很容易构建它。由于它变得更广泛,大多数依赖于StrxCPY的一些语义的代码,但使用其他功能的代码应该迁移到Memccpy,理想情况下,推动逐步淘汰它们将推动标准化和采用。许多广泛适用的字符串函数。

问题?注释?更正?随意在[email protected]与我联系;我喜欢听到你的想法!