创建 C 子字符串:使用赋值运算符 VS strncopy 循环,哪个更好?
Posted
技术标签:
【中文标题】创建 C 子字符串:使用赋值运算符 VS strncopy 循环,哪个更好?【英文标题】:Creating C substrings: looping with assignment operator VS strncopy, which is better? 【发布时间】:2012-08-30 07:19:41 【问题描述】:这可能有点毫无意义,但我很好奇你们对此有何看法。我正在使用指针迭代一个字符串,并希望从中提取一个短子字符串(将子字符串放入预先分配的临时数组中)。是否有任何理由在 strncopy 上使用赋值,反之亦然? IE。
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int main()
char orig[] = "Hello. I am looking for Molly.";
/* Strings to store the copies
* Pretend that strings had some prior value, ensure null-termination */
char cpy1[4] = "huh\0";
char cpy2[4] = "huh\0";
/* Pointer to simulate iteration over a string */
char *startptr = orig + 2;
int length = 3;
int i;
/* Using strncopy */
strncpy(cpy1, startptr, length);
/* Using assignment operator */
for (i = 0; i < length; i++)
cpy2[i] = *(startptr + i);
/* Display Results */
printf("strncpy result:\n");
printf("%s\n\n", cpy1);
printf("loop result:\n");
printf("%s\n", cpy2);
在我看来,strncopy 的输入更少,也更容易阅读,但我看到人们提倡使用循环。有区别吗?这还重要吗?假设这是针对 i (0
参考:Strings in c, how to get subString、How to get substring in C、Difference between strncpy and memcpy?
【问题讨论】:
strncpy() 总是错误的。在找到使用它的理由之前避免使用它。 感谢您的有用评论。有人提供好的建议并解释他们的理由总是很好。 请阅读手册页中关于 strncpy() 的描述。问问自己你真正想要它的哪些“功能”。然后问问自己,这些功能中哪些是你真正不想要的。 我做到了。我想从原始字符串中提取一个 3 字符的子字符串,而不是尝试复制字符串的其余部分或弄乱最后一个字符,即 '\0' 空终止值。所以我在 strncpy、strlcpy 和迭代(可能还有其他一些我不知道的)之间做出了选择。由于我明确知道所有尺寸,我认为 strncpy 不会有问题。我在这里没有看到任何有问题的功能。 在您知道所有尺寸的情况下,memcpy(cpy1, startptr, length);
完全正确(在这种特殊情况下,它与您的 strncpy 完全相同)。它还告诉人类读者您知道自己在做什么(并且您不想要一个 nul 终止符,因为您依赖于现有的终止符)在 (strlen(2nd argument) < length)
的情况下,两者都会以自己的特定方式失败。
【参考方案1】:
strncpy(char * dst, char *src, size_t len)
有两个特殊的属性:
(strlen(src) >= len)
:生成的字符串不会以 nul 结尾。
如果 (strlen(src) < len)
:字符串的末尾将被填充/填充 '\0'。
第一个属性将强制您实际检查(strlen(src) >= len)
并采取适当的行动。 (或使用dst[len-1] = '\0';
将最终字符粗暴地设置为 nul,就像上面的@Gilles 所做的那样)另一个属性并不是特别危险,但可能会溢出很多循环。想象一下:
char buff[10000];
strncpy(buff, "Hello!", sizeof buff);
它触及 10000 个字节,其中只需要触及 7 个。
我的建议:
答:如果你知道尺寸,就做memcpy(dst,src,len); dst[len] = 0;
B:如果您不知道大小,请以某种方式获取它们(使用 strlen 和/或 sizeof 和/或为动态分配的内存分配的大小)。然后:转到上面的 A。
因为为了安全操作 strncpy() 版本已经需要知道大小(以及对它们的检查!),所以 memcpy() 版本并不比 strncpy() 版本更复杂或更危险。 (从技术上讲,它甚至更快;因为 memcpy() 不必检查 '\0' 字节)
【讨论】:
【参考方案2】:虽然这似乎违反直觉,但复制字符串的方法比在循环中使用赋值运算符更优化。例如,IA-32 为 MOVS
、STOS
、CMPS
等提供了 REP
前缀用于字符串处理,这些可能比一次复制一个字符的循环快得多。 strncpy
或strcpy
的实现可以选择使用这种硬件优化的代码来获得更好的性能。
【讨论】:
所以简而言之,一般尽量使用内置方法,因为它们比我聪明? ;) 明白了。我怀疑这可能是这种情况(strncpy 可能能够在引擎盖下进行优化),但我通常是 CS 的新手,所以我想问问专家。谢谢! 看看gnu的libc源代码。优化后的代码尝试读取/写入 int 范围的对象,并且完成了所有工作以正确对齐。 @wildplasser ,你有什么建议?到目前为止,您所说的“避免 strncpy”虽然可能很有启发性,但并未提出替代解决方案。 也许我应该添加一个答案。 顺便说一句:6086 的 REP/REPZ 前缀操作码很快。286 改变了情况。在 386 之后,这些操作码与普通循环相比没有优势,因为内存带宽将始终是简单操作的瓶颈(现在几乎每个操作都很简单)。另外,编译器不会喜欢这些指令,因为隐式使用了 SI、DI 和 CX。【参考方案3】:只要您知道您的长度在“范围内”并且所有内容都正确地以 nul 终止,那么strncpy
会更好。
如果您需要在其中进行长度检查等,循环可能会更方便。
【讨论】:
【参考方案4】:带有赋值的循环是个坏主意,因为您正在重新发明***。你可能会犯错,你的代码可能比标准库中的代码效率低(一些处理器已经优化了内存复制指令,并且优化的实现通常至少在可能的情况下逐字复制)。
但是,请注意strncpy
不是一个全面的***。特别是,如果字符串太长,它不会将空字节附加到目标。 BSD 函数strlcpy
设计得更好,但并非随处可用。甚至strlcpy
is not a panacea:你需要正确设置缓冲区大小,并注意它可能会截断字符串。
复制字符串的一种可移植方式(如果字符串太长则截断)是调用strncpy
并始终添加终止的空字节。如果缓冲区是一个数组:
char buffer[BUFFER_SIZE];
strncpy(buffer, source, sizeof(buffer)-1);
buf[sizeof(buffer)-1] = 0;
如果缓冲区由指针和大小给出:
strncpy(buf, source, buffer_size-1);
buf[buffer_size-1] = 0;
【讨论】:
感谢您的信息!我已经阅读了一堆关于 strncpy 及其可能的“不当行为”的内容,但在这里我确定空终止。不过,如果我不明确知道缓冲区的大小,我会记住这一点! @surfreak 如果您已经知道源字符串的大小,并且知道它适合目标缓冲区,则可以使用strcpy
。最好使用assert
或评论提醒读者(可能还有运行时系统)尺寸要求。
是的,但是我在这里拉了一个子字符串,据我所知,如果子字符串在源字符串结束之前终止,strncpy
没有办法做到这一点......或者在那里?
@surfreak 哦,对。对于子字符串,如果您已经知道它适合目标,则可以使用 memcpy
并添加终止空字节。如果您不知道它是否合适,请使用strlcpy
(如果有),否则使用strncpy
(并添加终止空字节)。
所以现在的问题是在 memcpy 和 strncpy 之间......这有关系吗?无论哪种方式,我都必须添加空字节。以上是关于创建 C 子字符串:使用赋值运算符 VS strncopy 循环,哪个更好?的主要内容,如果未能解决你的问题,请参考以下文章