为什么C11标准没有删除不安全的strcat()和strcpy()函数?

4

C11C++14 标准已经取消了本质上不安全且容易导致安全问题的 gets() 函数,因为它没有执行边界检查,导致缓冲区溢出。那么为什么 C11 标准没有取消 strcat()strcpy() 函数呢?strcat() 函数没有检查第二个字符串是否适合第一个数组。 strcpy() 函数也没有检查目标数组的边界。如果源数组中的字符数超过目标数组的容量,那么程序很可能在运行时崩溃。

既然这两个不安全的函数容易导致问题,完全将它们从语言中删除不是更好吗?为什么它们仍然存在?原因是什么?只保留像 strncat(),strncpy() 这样的函数不是更好吗?如果我没记错,微软的 C 和 C++ 编译器提供了这些函数的安全版本 strcpy_s(),strcat_s()。那么为什么其他 C 编译器没有正式实现它们以提供安全性呢?


2
我相信xkcd在《标准》一文中已经介绍了这个问题,另外还请参考strncpystrncat - Elliott Frisch
8
我猜是因为gets永远不在程序员的控制之下,而strcpystrcat可以在程序员控制长度并知道他们在做什么的情况下使用。 - nhahtdh
1
如果标准是放弃所有可能被错误使用的函数,那么剩下的函数将非常少。 - riodoro1
@SouravGhosh:不,它不能从C中删除。作为程序员,您有责任确保对数组元素的每次访问都不超出数组的边界。这是为了获得性能而付出的代价。执行运行时检查可能会极大地影响程序的性能。因此,这是我们作为程序员需要接受的事实。 - Destructor
@meet 我的问题本来就是修辞性的,你意识到你自己已经回答了这个问题了吗? - Sourav Ghosh
显示剩余5条评论
5个回答

11

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' 空字符,导致下次将其传递给期望指向字符串的函数时出现未定义行为。正如 我曾经写过的


顺便提一句,我有理由相信原来的 gets() 在 1024 个字符处停止,但它没有写入标准,后来的 gets() 没有停止。 我偶尔会遇到停止的 gets()。 - Joshua
我能找到的最早的 gets() 实现是来自于1975年的V6 UNIX。它没有这样的限制。可能会有tty驱动程序所施加的限制,但这不会影响从文件重定向其stdin的程序。http://minnie.tuhs.org/cgi-bin/utree.pl?file=V6/usr/source/iolib/gets.c - Keith Thompson

5

strcpystrcatgets不相似。 gets的问题在于,它用于从输入读取数据,因此程序员无法控制是否会发生缓冲区溢出。


C99标准解释了strncpy

国际标准理由——编程语言——C §7.21.2.4 strncpy函数

strncpy最初被引入C库以处理结构中的固定长度名称字段,例如目录条目。这些字段的使用方式与字符串不同:对于最大长度字段,尾随空值是不必要的,并且将尾随字节设置为较短名称的空值可确保有效的逐个字段比较。strncpy并非起源于“有界的strcpy”,委员会更喜欢承认现有的做法,而不是改变函数以更好地适应这种用法。


很好,我不知道为什么他们保留了strncpy的原因在理论上有记录。 - Lundin

3
  • 神话1:strcpy()是不安全的,它的工作方式让经验丰富的C程序员大吃一惊。
  • 神话2:strncpy()是安全的。
  • 神话3:strncpy()是strcpy()的更安全版本。
  • 神话4:微软是C语言使用方面的权威,他们知道自己在说什么。

strcat()strcpy()完全安全的函数

请注意,strncpy从未被设计为strcpy的安全版本。它用于一个在古老版本的Unix中使用的模糊而过时的字符串格式。与strcpy不同,strncpy实际上非常不安全这里有很多关于它的博客文章),因为很少有程序员能够在不产生致命错误(无空终止符)的情况下使用前者。
更好的问题是,为什么固有不安全的strncpy()没有被从语言中移除。还有人在使用来自1970年代的模糊Unix字符串吗?

1
strncpy的格式不是过时的。对你来说可能很模糊,但对我来说不是。在处理某些类型的文件系统(包括ext2)时,您依赖于其行为。(我认为Linux内核在ext2中未使用strncpy,但它可能已经使用了,因为磁盘上的文件系统使用该格式。) - Joshua
当然,就标准C而言,它已经过时了。这个函数一开始就不应该被包含在语言中,而应该作为特定于操作系统的API的一部分保留下来。“它在*nix中使用”是让一个非通用、晦涩的函数成为标准库的很差的理由。 - Lundin
当 C 作为 Unix 启动时,它已经太晚了。他们本应该规范化 strlcpy 和 strlcat,但这些函数还未出现。 - Joshua

1
当完全删除一个函数时,标准主要考虑的是它可能破坏多少代码以及有多少人(程序员、库编写者、编译器供应商等)会对此变化感到恼怒或反对。
从LSB(Linux标准基础)中弃用了gets()。POSIX-2008将其废弃,gets()一直以来都被认为是一个严重的不良函数,并且一直强烈建议不要在任何代码中使用。几乎所有的C程序员都知道使用gets()是非常危险的。因此,它被删除对于委员会来说是很容易的,而且它的移除几乎不会破坏任何生产代码,甚至不存在这种可能性。
但对于strcpystrcat等函数来说情况并非如此。它们可以安全地使用,并且仍然被许多程序员用于新代码中。虽然它们可能会受到缓冲区溢出的影响,但大多数情况下是由程序员控制的,而gets()则不是。

可以使用 snprintf 替代 strcpystrcat,但在简单情况下似乎没有意义,比如:

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

(如果buf是指针,则需要跟踪分配大小以供snprintf使用)
因为作为程序员,我知道,上述方法是完全安全的。更重要的是,许多旧代码将会出现问题。基本上,没有强有力的论据可以支持删除strcpy等函数,因为它们可以安全地使用。

0

你所说的是会导致未定义行为的情况。

比方说

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

你有数组越界访问,标准没有移除这个问题,因为是你自己在分配值并且它在你的控制之下。 strcpy()strcat() 也是一样。
所以标准无法移除所有导致未定义行为的情况。
gets() 我们知道不受程序员控制,它从某个流中获取数据,你永远不知道输入可能是什么,很有可能会导致缓冲区溢出,因此它已被移除,并添加了一个更安全的函数 fgets()

2
你的示例中没有越界访问或未定义行为。 - P.P
@BlueMoon 想要展示一些数组越界访问的情况,结果却写出了内部访问的代码 :) - Gopi
3
char a[3] = "string"; 不是运行时缓冲区溢出,而是编译时检测到的约束违规。根据N1570 6.7.9p2规定:"任何初始化程序都不得为未被包含在正在初始化的实体中的对象提供值。" - Keith Thompson
@KeithThompson 是的,我明白 - Gopi
如果代码无法编译,那么就不会出现越界访问的情况。(一些编译器(包括默认模式下的gcc)仅对声明发出警告,但是编译器可以且我认为应该将其视为致命错误。) - Keith Thompson
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接