创建 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) &lt; length) 的情况下,两者都会以自己的特定方式失败。 【参考方案1】:

strncpy(char * dst, char *src, size_t len) 有两个特殊的属性:

如果 (strlen(src) &gt;= len) :生成的字符串不会以 nul 结尾。 如果 (strlen(src) &lt; len) :字符串的末尾将被填充/填充 '\0'。

第一个属性将强制您实际检查(strlen(src) &gt;= 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 为 MOVSSTOSCMPS 等提供了 REP 前缀用于字符串处理,这些可能比一次复制一个字符的循环快得多。 strncpystrcpy的实现可以选择使用这种硬件优化的代码来获得更好的性能。

【讨论】:

所以简而言之,一般尽量使用内置方法,因为它们比我聪明? ;) 明白了。我怀疑这可能是这种情况(strncpy 可能能够在引擎盖下进行优化),但我通常是 CS 的新手,所以我想问问专家。谢谢! 看看gnu的libc源代码。优化后的代码尝试读取/写入 int 范围的对象,并且完成了所有工作以正确对齐。 @wildplasser ,你有什么建议?到目前为止,您所说的“避免 strncpy”虽然可能很有启发性,但并未提出替代解决方案。 也许我应该添加一个答案。 顺便说一句:6086 的 REP/REPZ 前缀操作码很快。286 改变了情况。在 386 之后,这些操作码与普通循环相比没有优势,因为内存带宽将始终是简单操作的瓶颈(现在几乎每个操作都很简单)。另外,编译器不会喜欢这些指令,因为隐式使用了 SI、DI 和 CX。【参考方案3】:

只要您知道您的长度在“范围内”并且所有内容都正确地以 nul 终止,那么strncpy 会更好。

如果您需要在其中进行长度检查等,循环可能会更方便。

【讨论】:

【参考方案4】:

带有赋值的循环是个坏主意,因为您正在重新发明***。你可能会犯错,你的代码可能比标准库中的代码效率低(一些处理器已经优化了内存复制指令,并且优化的实现通常至少在可能的情况下逐字复制)。

但是,请注意strncpy 不是一个全面的***。特别是,如果字符串太长,它不会将空字节附加到目标。 BSD 函数strlcpy 设计得更好,但并非随处可用。甚至strlcpyis 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 循环,哪个更好?的主要内容,如果未能解决你的问题,请参考以下文章

拷贝构造函数 Vs 赋值运算函数

如何避免共享指针的复制赋值运算符c ++

为啥要避免使用递增赋值运算符 (+=) 创建集合

变量类型

绕过自动生成的赋值运算符(VS bug?)

Python3 几个常用的运算符