【问题标题】:Strncpy should only be used with fixed length arraysStrncpy 只能用于固定长度的数组
【发布时间】:2016-10-10 21:39:07
【问题描述】:

根据this StackOverflow 的评论,strncpy 永远不应与非固定长度数组一起使用。

strncpy 除非您在结构/二进制文件中使用固定宽度、非必要终止的字符串字段,否则不应使用。 – R.. 2012 年 1 月 11 日 16:22

我知道如果您为字符串动态分配内存是多余的,但是否有理由使用strncpy 而不是strcpy 会不好

【问题讨论】:

  • 您可以在这里找到答案 (stackoverflow.com/questions/1258550/…)
  • 原因是它没有做你认为它做的事情。
  • 你不应该将strncpy用于任何目的,它本质上是不安全的,see this。相反,将strcpy 与缓冲区大小检查一起使用。
  • @Lundin:结构具有需要容纳 4 或 8 个字符的固定长度字段的情况并不少见。任何坚持应用零终止的方法都将限制为 3 或 7 个字符,或者需要将字符串复制到外部缓冲区,然后使用 memcpy 来存储它。如果一个结构包含一个 char[8] 并且有必要将其初始化为“Hello”,我认为strncpy(thing.name,"Hello",8); 会比memcpy(thing.name, "Hello\0\0", 8); 更干净,更不易碎
  • @supercat 初始化一个结构:type_t type = { .str="Hello" };。运行时分配一个结构:type = (type) { .str="Hello" };。这与strncpy 没有太大关系,它仍然是一个危险的、过时的函数,仅用于 Unix 中的古老字符串类型。

标签: c++ c strcpy strncpy


【解决方案1】:

char * strncpy ( char * 目标, const char * 源, size_t num );

strncpy() 的限制:

  1. 如果完全填满,它不会在目标字符串上放置空终止符。而且,如果 source 长于 num,则不会在目标末尾隐式附加空字符。
  2. 如果 num 大于源字符串的长度,则用空字符填充目标字符串,直到 num 长度。

与 strcpy 一样,它不是内存安全操作。因为它在复制源之前没有检查目标中是否有足够的空间,所以它是缓冲区溢出的潜在原因。

参考:Why should you use strncpy instead of strcpy?

【讨论】:

  • 尤其是您的原始措辞“strncpy() 的奇怪之处”,这和第一点的开头似乎是从this answer 提升的。如果您确实使用了其他地方的东西,请引用、注明并链接到它。干杯。
  • 我在评论中提到了这个链接。顺便说一句,现在我也在我的答案中添加了这个。谢谢!!!
【解决方案2】:

我们有 2 个版本用于将字符串从一个复制到另一个 1> 字符串 2> strncpy 这两个版本用于固定和非固定长度数组。 strcpy 在复制字符串时不检查目标字符串的上限,strncpy 会检查它。当目标字符串达到这个上限时,函数strncpy会返回错误码,同时函数strcpy会在当前进程的内存中产生一些影响并立即终止进程。这样strncpy比strcpy更安全

【讨论】:

    【解决方案3】:

    strncpy 会将数据复制到您指定的限制 - 但如果它在字符串结尾之前达到该限制,它将离开目的地未终止

    换句话说,strncpy 有两种可能性。一个是你得到的行为与strcpy 无论如何都会产生的行为完全相同(除了速度较慢,因为它用 NUL 填充目标缓冲区的其余部分,而你实际上从未真正想要或关心它)。另一个是它会产生一个你通常无法真正使用的结果。

    如果你想将一个最大长度的字符串复制到一个固定长度的缓冲区中,你可以(例如)使用sprintf 来完成这项工作:

    char buffer[256];
    
    sprintf(buffer, "%255s", source);
    

    strncpy 不同,这个总是以零结尾的结果,所以结果总是可以用作字符串。

    如果您不想使用sprintf(或类似的),我建议您只编写一个实际执行您想要的功能的函数,按照这个一般顺序:

    void copy_string(char const *dest, char const *source, size_t max_len) { 
        size_t i;
        for (i=0; i<max_len-1 && source[i]; i++)
            dest[i] = source[i];
        dest[i] = '\0';
    }
    

    由于您已将其标记为 C++(除了 C):我的建议是通常通过使用 std::string 避免 C++ 中的整个混乱。

    如果您真的必须在 C++ 中使用以 NUL 结尾的序列,您可能会考虑另一种可能性:

    template <size_t N>
    void copy_string(char const (&dest)[N], char const *source) {
        size_t i;
        for (i=0; i<N-1 && source[i]; i++)
            dest[i] = source[i];
        dest[i] = '\0';
    }
    

    这仅在目标是实际数组(不是指针)时有效,但在这种情况下,它让编译器推断数组的大小,而不是要求用户显式传递它。这通常会使代码更快一点(函数调用的开销更少)并且更难搞砸和传递错误的大小。

    【讨论】:

    • 你也可以手动终止它:strncpy(dest,source,size-1); dest[size] = '\0';。也许不是那么优雅,但我更喜欢它而不是sprintf
    • @AndreiR.: 一般的观点很好(经常这样做),但是dest[size]? ;-)
    • @AndreiR.:添加了备选答案。
    • 在我正在处理的部分代码中,我似乎一直在使用char dest[256]; if (snprintf(dest,256,"Value: %s",source) &gt;= 256) {\\do error mitigation}。我只是有点困惑,因为我一直在追查这个由糟糕的strcpy 引起的错误,而我睡眠不足的大脑只是将其更改为strncpy,以为它会修复它,但它没有,我去了想弄清楚我之前的所有逻辑是否都错了。
    【解决方案4】:

    反对使用strncpy 的理由是它不能保证你的字符串会被空终止。

    在使用非固定长度数组时,在 C 中复制字符串的不易出错的方法是使用 snprintf,它可以保证字符串的空终止。

    一个很好的Blog Post 评论*n* 函数。

    这些函数可以让你指定缓冲区的大小,但是——这真的很重要——它们不保证空终止。如果您要求这些函数写入比填充缓冲区更多的字符,那么它们将停止——从而避免缓冲区溢出——但它们不会以空值终止缓冲区。

    这意味着在不处理固定数组时使用strncpy 和其他此类函数会引入非空终止字符串的不必要风险,这可能是代码中的定时炸弹。

    【讨论】:

    • “不易出错” - 是的,但正确检查你的输出是否完整仍然很难看:int n = snprintf(buffer, sizeof buffer, "%s", source); assert(n &gt;= 0 &amp;&amp; n &lt; sizeof buffer);strncpy(buffer, source, sizeof buffer); assert(buffer[sizeof buffer-1] == '\0');(可能是 strncpy 填充带有 NUL 的完整缓冲区)。
    • @TonyD 我想是风格问题?我个人更喜欢第一个assert,因为您没有进行数组访问,而且您还可以访问写入的字符数。
    • 是的 - 风格和具体情况 - 例如。反驳n 可能有用,如果不需要,它可能会弄乱你的本地符号,你很快就会玩弄n2n3op 或使用更详细的this_lenthat_len .
    猜你喜欢
    • 2011-05-30
    • 2011-05-18
    • 1970-01-01
    • 2019-02-28
    • 2020-08-31
    • 2012-08-26
    • 2011-02-16
    • 2020-03-11
    相关资源
    最近更新 更多