【问题标题】:Why C11 standard doesn't drop unsafe strcat(),strcpy() functions?为什么 C11 标准不删除不安全的 strcat(),strcpy() 函数?
【发布时间】:2015-09-02 04:24:16
【问题描述】:

C11C++14 标准已经删除了 gets() 函数,该函数本质上是不安全的并导致安全问题,因为它不执行边界检查导致缓冲区溢出。那么为什么C11 标准不删除strcat()strcpy() 函数呢? strcat() 函数不检查第二个字符串是否适合第一个数组。 strcpy() 函数也不包含检查目标数组边界的规定。如果源数组的字符多于目标数组可以容纳的字符怎么办?很可能程序会在运行时崩溃。

那么,如果将这两个不安全的函数从语言中完全删除不是很好吗?为什么它们仍然存在?是什么原因?只有strncat(),strncpy()这样的功能不是很好吗?如果我没记错的话,Microsoft C & C++ 编译器提供了这些函数的安全版本strcpy_s(),strcat_s()。那为什么其他 C 编译器没有正式实现它们以提供安全性呢?

【问题讨论】:

  • 我相信 xkcd 用Standards 报道了这个问题,另见strncpystrncat
  • 我猜是因为gets 永远不在程序员的控制范围内,而strcpystrcat 可以用在程序员可以控制长度并知道自己在做什么的地方。
  • 如果标准是放弃所有可以错误使用的功能,那么剩下的功能就很少了。
  • 在这方面,数组在C 中没有绑定检查,使其有点不安全,所以可以从C 中删除它吗? :-)
  • 当我输入“man gets”时,我看到标签“(deprecated)”。当我输入“man strcpy”时,我看不到这样的标签。可能他们只是从标准库中删除了已弃用的函数,因为它们已被声明为弃用。

标签: c strcpy c11 strcat


【解决方案1】:

gets() 本质上是不安全的,因为通常如果在stdin 上接收到太多数据,它可能会溢出目标。这个:

char s[MANY];
gets(s);

如果输入超过MANY 个字符,将导致未定义的行为,并且程序通常无法阻止它。

strcpy()strcat() 可以完全安全地使用,因为只有当源字符串太长而无法包含在目标数组中时,它们才会溢出目标。源字符串包含在一个数组对象中,该对象受程序本身的控制,而不是任何外部输入。例如,这个:

char s[100];
strcpy(s, "hello");
strcat(s, ", ");
strcat(s, "world");

除非程序本身被修改,否则不可能溢出。

strncat() 可以用作strcat() 的更安全版本——只要您正确指定第三个参数。 strncat() 的一个问题是它只为您提供了一种方法来处理目标数组中没有足够空间的情况:它默默地截断字符串。有时这可能是您想要的,但有时您可能想要检测溢出并对其采取一些措施。

至于strncpy(),它只是strcpy() 的更安全版本。它本身并不危险,但如果你不是很小心,你可以很容易地离开目标数组而没有终止 '\0' 空字符,从而在下次将它传递给期望指向字符串的指针的函数时导致未定义的行为。碰巧,I've written about this

【讨论】:

  • 顺便说一句;我有理由相信原始的 gets() 停止在 1024 个字符处,但它没有写入标准,所以后来的 gets() 并没有停止。我遇到了时不时停止的gets()。
  • 我能找到的较早的 gets() 实现来自 1975 年的 V6 UNIX。它没有这样的限制。 tty 驱动程序可能施加了限制,但这不会影响其标准输入从文件重定向的程序。 minnie.tuhs.org/cgi-bin/utree.pl?file=V6/usr/source/iolib/…
【解决方案2】:

strcpystrcatgets 不同。 gets的问题是,它是用来从输入中读取的,所以缓冲区溢出是程序员无法控制的。


C99 Rational 将strncpy 解释为:

Rationale for International Standard — Programming Languages — C §7.21.2.4 strncpy 函数

strncpy 最初被引入到 C 库中,用于处理目录条目等结构中的固定长度名称字段。此类字段的使用方式与字符串不同:对于最大长度字段,尾随 null 是不必要的,将较短的 5 个名称的尾随字节设置为 null 可确保有效的逐字段比较。 strncpy 的起源并不是“有界的strcpy,委员会更愿意承认现有的做法,而不是改变功能以更好地适应这种用途。

【讨论】:

  • 很好,我不知道他们保留 strncpy 的原因记录在理由中。
【解决方案3】:
  • 误区 1:strcpy() 是不安全的,它的工作原理让资深 C 程序员大吃一惊。
  • 误区 2:strncpy() 是安全的。
  • 误区 3:strncpy() 是 strcpy() 的更安全版本。
  • 误区 4:微软是某种使用 C 语言的权威并且知道他们在说什么。

strcat()strcpy()perfectly safe functions

还要注意strncpy was never intended to be a safe version of strcpy。它用于古老版本的 Unix 中使用的晦涩、过时的字符串格式。 strncpy 实际上是 very unsafe (one of many blog post about it here),与 strcpy 不同,因为似乎很少有程序员能够使用前者而不产生致命错误(无空终止)。

一个更好的问题是为什么固有不安全的strncpy() 没有从语言中删除。是否有人经常使用 1970 年代晦涩的 Unix 字符串?

【讨论】:

  • strncpy 的格式没有过时。这对你来说很模糊,但对我来说并不模糊。在使用某些类型的文件系统(包括 ext2)时,您依赖于它的行为。 (我不认为 Linux 内核在 ext2 中使用 strncpy,但它可能有,因为磁盘上的文件系统使用该格式。)
  • @Joshua 当然,就标准 C 而言,它已经过时了。该函数一开始就不应该包含在语言中,而应该作为特定于操作系统的 API 的一部分。 “它在 *nix 中使用”是让非泛型、晦涩的函数成为标准库的一部分的非常糟糕的理由。
  • 当 C 被引导出 Unix 时,已经太晚了。他们应该有标准化的 strlcpy 和 strlcat,但他们还没有出现。
【解决方案4】:

完全删除一个函数时,标准必须主要考虑的主要事情之一是它可以破坏多少代码以及有多少人(程序员、库编写者、编译器供应商等)会对改变感到恼火(或反对)。

gets() 已从 LSB(Linux 标准库)中弃用。 POSIX-2008 使其过时,gets() 在历史上一直被认为是一个严重 糟糕的函数,并且一直强烈建议不要在任何代码中使用它。几乎每个 C 程序员都知道使用gets() 是非常危险的。因此,删除它会破坏任何生产代码的可能性非常小,它不,不存在。因此,委员会很容易从 C11 中删除 gets()

strcpystrcat 等情况并非如此。它们可以安全使用,并且仍然被许多程序员在新代码中使用。虽然它们可能会受到缓冲区溢出的影响,但这主要是程序员的控制,而gets() 则不是。

可以使用snprintf 代替strcpystrcat。但在以下简单情况下似乎毫无意义:

char buf[256];
strcpy(buf, "hello");

(如果buf是一个指针,则需要跟踪分配大小以供snprintf使用)

因为作为一名程序员,我知道,以上是绝对安全的。更重要的是,很多 遗留代码会被破坏。基本上,没有这么强的论据可以删除strcpy等函数,因为它们可以安全使用。

【讨论】:

    【解决方案5】:

    您所说的是会导致未定义行为的场景。

    我们说

    char a[3] = "string";
    for(i=0;i<5;i++)
    printf("%c\n",a[i]);
    

    您的数组越界访问,标准并没有删除它,因为是您在分配值并且它在您的控制之下。

    strcpy()strcat() 相同。

    所以标准不能删除所有导致 UB 的场景。

    gets() 我们知道它不受程序员的控制,它正在从某个流中获取数据,而您永远不知道输入可能是什么,并且很有可能最终导致缓冲区溢出,因此已将其删除并添加了更安全的函数fgets()

    【讨论】:

    • 您的示例中没有越界访问或 UB。
    • @BlueMoon 想要显示一些数组越界访问并最终编写入站访问代码:)
    • char a[3] = "string"; 不是运行时缓冲区溢出,而是将在编译时检测到的约束冲突N1570 6.7.9p2:“任何初始化程序都不应尝试为未包含在正在初始化的实体中的对象提供值。”
    • @KeithThompson 是的,我明白了
    • @KeithThompson 是的,我通过展示越界访问明确了我的观点,这应该是好的
    猜你喜欢
    • 2011-08-13
    • 2010-10-30
    • 2014-06-12
    • 2013-08-11
    • 2015-02-20
    • 2020-04-02
    • 1970-01-01
    • 1970-01-01
    • 2010-12-30
    相关资源
    最近更新 更多