【问题标题】:strncpy doesn't always null-terminatestrncpy 并不总是空终止
【发布时间】:2013-06-13 20:12:53
【问题描述】:

我正在使用下面的代码:

char filename[ 255 ];
strncpy( filename, getenv( "HOME" ), 235 );
strncat( filename, "/.config/stationlist.xml", 255 );

收到此消息:

(warning) Dangerous usage of strncat - 3rd parameter is the maximum number of characters to append.
(error) Dangerous usage of 'filename' (strncpy doesn't always null-terminate it).

【问题讨论】:

  • 您的实际问题是什么?
  • 只是好奇,但是哪个编译器会给出这些消息?
  • @andrewcooke Google 说它是 cppcheck 静态分析器
  • 谢谢。看起来很有用。
  • 危险使用getenv():会返回NULL,导致危险使用str[n]cpy();

标签: c strncpy


【解决方案1】:

我通常避免使用str*cpy()str*cat()。您必须应对边界条件、晦涩难懂的 API 定义和意想不到的性能后果。

您可以改用snprintf()。您只需要与目标缓冲区的大小抗衡。而且,更安全的是它不会溢出,并且总是会为你终止 NUL。

char filename[255];
const char *home = getenv("HOME");
if (home == 0) home = ".";
int r = snprintf(filename, sizeof(filename), "%s%s", home, "/.config/stationlist.xml");
if (r >= sizeof(filename)) {
    /* need a bigger filename buffer... */
} else if (r < 0) {
    /* handle error... */
}

【讨论】:

  • 有趣的解决方案,但是解析格式字符串 ("%s%s") 和 vararg 怎么能比从 A 复制字节到 B 更快?
  • @Oliver:当我谈论性能后果时,我指的是连续重复调用str*cat() 的代码,而不是单个snprintf() 调用来完成相同的任务。但你是对的,在性能敏感的代码路径中过于频繁地调用snprintf() 并不是一个好主意。我认为形成配置文件的路径不是问题。
【解决方案2】:

您的strncat 呼叫可能会溢出filename

用途:

strncat(filename, "/.config/stationlist.xml",
        sizeof filename - strlen(filename) - 1);

还要确保在strncpy 调用后终止缓冲区:

strncpy( filename, getenv( "HOME" ), 235 );
filename[235] = '\0';

如果源的长度大于或等于要复制的最大字符数,strncpy 不会终止其目标缓冲区。

【讨论】:

  • 最后一句话需要打印在此方法的每一页文档中。
【解决方案3】:

man strncpy 有话要说:

Warning: If there is no null byte among the first n bytes
of src, the string placed in dest will not be null terminated.

如果它在用完最大长度之前遇到源中的 0 字节,它将被复制。但是如果在源中的第一个 0 之前达到最大长度,则不会终止目标。最好在strncpy()返回后确定是你自己...

【讨论】:

    【解决方案4】:

    strncpy() 和(甚至更多)strncat() 都有不明显的行为,最好不要使用任何一个。

    strncpy()

    如果您的目标字符串长度为 255 个字节,strncpy() 将始终写入所有 255 个字节。如果源字符串短于 255 个字节,它将用零填充剩余部分。如果源字符串超过 255 个字节,它将在 255 个字节后停止复制,使目标没有空终止符。

    strncat()

    大多数“大小”函数(strncpy()memcpy()memmove() 等)的大小参数是目标字符串(内存)中的字节数。对于strncat(),大小是在目标中已经存在的字符串末尾之后剩余的空间量。因此,只有知道目标缓冲区有多大(S)和目标字符串当前有多长(L)时,才能安全地使用strncat()strncat() 的安全参数是 S-L(我们会担心其他时间是否存在不匹配)。但鉴于您知道L,让strncat() 跳过L 字符是没有意义的;你可以通过target+L 作为开始的地方,然后简单地复制数据。你可以使用memmove()memcpy(),或者你可以使用strcpy(),甚至strncpy()。如果您不知道源字符串的长度,您必须确信截断它是有意义的。

    问题代码分析

    char filename[255];
    strncpy(filename, getenv("HOME"), 235);
    strncat(filename, "/.config/stationlist.xml", 255);
    

    除非大小被认为太小(或者您在未设置$HOME 的上下文中运行程序),否则第一行是正常的,但这超出了本问题的范围。对strncpy() 的调用不使用sizeof(filename) 作为大小,而是使用任意小的数字。这不是世界末日,但一般来说,不能保证变量的最后 20 个字节是零字节(甚至它们中的任何一个都是零字节)。在某些情况下(filename 是一个全局变量,以前未使用过)可能会保证为零。

    strncat() 调用尝试将 24 个字符附加到 filename 中的字符串末尾,这些字符可能已经是 232-234 字节长,或者可能任意长于 235 字节。无论哪种方式,这都是保证缓冲区溢出。 strncat() 的使用也直接落入了关于它的大小的陷阱。您说过可以在 filename 中已有内容的末尾添加最多 255 个字符,这是明显错误的(除非来自 getenv("HOME") 的字符串恰好是空的)。

    更安全的代码:

    char filename[255];
    static const char config_file[] = "/.config/stationlist.xml";
    const char *home = getenv("HOME");
    size_t len = strlen(home);
    if (len > sizeof(filename) - sizeof(config_file))
        ...error file name will be too long...
    else
    {
        memmove(filename, home, len);
        memmove(filename+len, config_file, sizeof(config_file));
    }
    

    有些人坚持认为“memcpy() 是安全的,因为字符串不能重叠”,并且在某种程度上他们是正确的,但重叠应该不是问题,而对于 memmove(),这是一个非问题-问题。所以,我一直使用memmove()……但我还没有进行时间测量,看看它有多大的问题,如果它是一个问题的话。也许其他人已经完成了测量。

    总结

    1. 不要使用strncat()
    2. 谨慎使用strncpy()(注意它在非常大的缓冲区上的行为!)。
    3. 计划改用memmove()memcpy();如果您可以安全地进行复制,那么您就知道合理的必要尺寸。

    【讨论】:

      【解决方案5】:

      1) 您的 strncpy 不一定以空值终止文件名。事实上,如果 getenv("HOME") 超过 235 个字符并且 getenv("HOME")[234] 不是 0,它不会。 2) 您的 strncat() 可能会尝试将文件名扩展超过 255 个字符,因为正如它所说,

      3rd parameter is the maximum number of characters to append. 
      

      (不是dst允许的总长度)

      【讨论】:

      • 如果文件名是一个自动变量,它将不会被初始化(或者你称之为归零)
      【解决方案6】:

      strncpy(Copied_to,Copied_from,sizeof_input) 在字符数组之后输出垃圾值(不用于字符串类型)。使用遍历字符数组的for循环来解决它的输出,而不是简单地使用cout&lt;&lt;var;

      for(i=0;i<size;i++){cout<<var[i]} 
      

      我找不到使用 minGW 编译器在 Windows 系统上进行遍历的解决方法。 空终止并不能解决问题。在线编译器工作得很好。

      【讨论】:

        猜你喜欢
        • 2010-11-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-02-16
        • 1970-01-01
        • 1970-01-01
        • 2019-07-09
        • 2015-12-26
        相关资源
        最近更新 更多