为什么 C11 标准不删除不安全的 strcat()、strcpy() 函数?
Why C11 standard doesn't drop unsafe strcat(),strcpy() functions?
C11
& C++14
标准已经删除了 gets()
功能,该功能本质上是不安全的并且会导致安全问题,因为它不会在缓冲区溢出时执行边界检查结果。那为什么 C11
标准不删除 strcat()
和 strcpy()
函数? strcat()
函数不检查第二个字符串是否适合第一个数组。 strcpy()
函数也不包含检查目标数组边界的规定。如果源数组的字符多于目标数组可以容纳的字符怎么办?很可能程序会在运行时崩溃。
那么,如果这两个不安全的函数从语言中完全移除不是更好吗?为什么它们还存在?是什么原因?只有strncat(),strncpy()
这样的函数不是很好吗?如果我没记错的话,Microsoft C & C++ 编译器提供了这些函数的安全版本 strcpy_s(),strcat_s()
。那么为什么它们没有被其他 C 编译器正式实现以提供安全性?
你说的是会导致未定义行为的场景。
假设
char a[3] = "string";
for(i=0;i<5;i++)
printf("%c\n",a[i]);
你有数组越界访问,标准没有删除它,因为是你在分配值并且它在你的控制之下。
与 strcpy()
和 strcat()
相同。
因此标准无法删除导致 UB 的所有场景。
而我们知道 gets()
不在程序员的控制之下,它从某个流中获取数据,而您永远不知道输入可能是什么,并且很有可能最终导致缓冲区溢出,所以它已被删除并添加了更安全的功能 fgets()
。
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()
的更安全版本。它本身并不危险,但如果您不是很小心,您可以轻松地离开目标数组而没有终止 '[=22=]'
空字符,从而导致下次将其传递给需要指向字符串指针的函数时出现未定义的行为。碰巧,I've written about this.
strcpy
和 strcat
与 gets
不相似。 gets
的问题是,它是用来读取输入的,所以是否会出现缓冲区溢出是程序员无法控制的。
C99 Rational 解释 strncpy
为:
Rationale for International Standard — Programming Languages — C §7.21.2.4 The strncpy
function
strncpy
was initially introduced into the C library to deal with fixed-length name fields in structures such as directory entries. Such fields are not used in the same way as strings: the trailing null is unnecessary for a maximum-length field, and setting trailing bytes for shorter 5 names to null assures efficient field-wise comparisons. strncpy
is not by origin a “bounded strcpy
,” and the Committee preferred to recognize existing practice rather than alter the function to better suit it to such use.
当完全删除一个函数时,标准必须主要考虑的主要事情之一是它可能破坏多少代码以及有多少人(程序员、库编写者、编译器供应商等)会对这种变化感到恼火(或反对)。
gets()
已从 LSB(Linux 标准基础)中弃用。 POSIX-2008 使其过时并且 gets()
在历史上被认为是一个 严重 错误的函数并且一直强烈反对在任何代码中使用。几乎每个 C 程序员都知道使用 gets()
是非常危险的。因此,删除它破坏任何生产代码的可能性非常小,它不存在。因此,委员会很容易从 C11 中删除 gets()
。
但strcpy
、strcat
等就不是这样了。它们可以安全使用,并且仍然被许多程序员在新代码中使用。虽然它们可能会受到缓冲区溢出的影响,但它主要是程序员的控制,而 gets()
则不是。
可以使用 snprintf
代替 strcpy
和 strcat
。但在像这样的简单情况下,这似乎毫无意义:
char buf[256];
strcpy(buf, "hello");
(如果 buf
是指针,则需要跟踪分配大小以便在 snprintf
中使用)
因为作为程序员,我知道,以上是绝对安全的。更重要的是,lot 遗留代码会被破坏。基本上,没有强有力的理由可以删除 strcpy
等函数,因为它们可以安全使用。
- 误区 1:strcpy() 不安全,它的工作原理令资深 C 程序员大吃一惊。
- 误区 2:strncpy() 是安全的。
- 误区 3:strncpy() 是 strcpy() 的更安全版本。
- 误区四:微软是某种使用C语言的权威,知道他们在说什么。
strcat()
和 strcpy()
是 perfectly safe functions。
另请注意 strncpy
was never intended to be a safe version of strcpy. It is used for an obscure, obsolete string format used in an ancient version of Unix. strncpy
is actually very unsafe (one of many blog post about it here),这与 strcpy
不同,因为似乎很少有程序员能够在不产生致命错误(无空终止)的情况下使用前者。
一个更好的问题是为什么没有从语言中删除固有的不安全 strncpy()
。有人经常使用 1970 年代晦涩难懂的 Unix 字符串吗?
C11
& C++14
标准已经删除了 gets()
功能,该功能本质上是不安全的并且会导致安全问题,因为它不会在缓冲区溢出时执行边界检查结果。那为什么 C11
标准不删除 strcat()
和 strcpy()
函数? strcat()
函数不检查第二个字符串是否适合第一个数组。 strcpy()
函数也不包含检查目标数组边界的规定。如果源数组的字符多于目标数组可以容纳的字符怎么办?很可能程序会在运行时崩溃。
那么,如果这两个不安全的函数从语言中完全移除不是更好吗?为什么它们还存在?是什么原因?只有strncat(),strncpy()
这样的函数不是很好吗?如果我没记错的话,Microsoft C & C++ 编译器提供了这些函数的安全版本 strcpy_s(),strcat_s()
。那么为什么它们没有被其他 C 编译器正式实现以提供安全性?
你说的是会导致未定义行为的场景。
假设
char a[3] = "string";
for(i=0;i<5;i++)
printf("%c\n",a[i]);
你有数组越界访问,标准没有删除它,因为是你在分配值并且它在你的控制之下。
与 strcpy()
和 strcat()
相同。
因此标准无法删除导致 UB 的所有场景。
而我们知道 gets()
不在程序员的控制之下,它从某个流中获取数据,而您永远不知道输入可能是什么,并且很有可能最终导致缓冲区溢出,所以它已被删除并添加了更安全的功能 fgets()
。
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()
的更安全版本。它本身并不危险,但如果您不是很小心,您可以轻松地离开目标数组而没有终止 '[=22=]'
空字符,从而导致下次将其传递给需要指向字符串指针的函数时出现未定义的行为。碰巧,I've written about this.
strcpy
和 strcat
与 gets
不相似。 gets
的问题是,它是用来读取输入的,所以是否会出现缓冲区溢出是程序员无法控制的。
C99 Rational 解释 strncpy
为:
Rationale for International Standard — Programming Languages — C §7.21.2.4 The
strncpy
function
strncpy
was initially introduced into the C library to deal with fixed-length name fields in structures such as directory entries. Such fields are not used in the same way as strings: the trailing null is unnecessary for a maximum-length field, and setting trailing bytes for shorter 5 names to null assures efficient field-wise comparisons.strncpy
is not by origin a “boundedstrcpy
,” and the Committee preferred to recognize existing practice rather than alter the function to better suit it to such use.
当完全删除一个函数时,标准必须主要考虑的主要事情之一是它可能破坏多少代码以及有多少人(程序员、库编写者、编译器供应商等)会对这种变化感到恼火(或反对)。
gets()
已从 LSB(Linux 标准基础)中弃用。 POSIX-2008 使其过时并且 gets()
在历史上被认为是一个 严重 错误的函数并且一直强烈反对在任何代码中使用。几乎每个 C 程序员都知道使用 gets()
是非常危险的。因此,删除它破坏任何生产代码的可能性非常小,它不存在。因此,委员会很容易从 C11 中删除 gets()
。
但strcpy
、strcat
等就不是这样了。它们可以安全使用,并且仍然被许多程序员在新代码中使用。虽然它们可能会受到缓冲区溢出的影响,但它主要是程序员的控制,而 gets()
则不是。
可以使用 snprintf
代替 strcpy
和 strcat
。但在像这样的简单情况下,这似乎毫无意义:
char buf[256];
strcpy(buf, "hello");
(如果 buf
是指针,则需要跟踪分配大小以便在 snprintf
中使用)
因为作为程序员,我知道,以上是绝对安全的。更重要的是,lot 遗留代码会被破坏。基本上,没有强有力的理由可以删除 strcpy
等函数,因为它们可以安全使用。
- 误区 1:strcpy() 不安全,它的工作原理令资深 C 程序员大吃一惊。
- 误区 2:strncpy() 是安全的。
- 误区 3:strncpy() 是 strcpy() 的更安全版本。
- 误区四:微软是某种使用C语言的权威,知道他们在说什么。
strcat()
和 strcpy()
是 perfectly safe functions。
另请注意 strncpy
was never intended to be a safe version of strcpy. It is used for an obscure, obsolete string format used in an ancient version of Unix. strncpy
is actually very unsafe (one of many blog post about it here),这与 strcpy
不同,因为似乎很少有程序员能够在不产生致命错误(无空终止)的情况下使用前者。
一个更好的问题是为什么没有从语言中删除固有的不安全 strncpy()
。有人经常使用 1970 年代晦涩难懂的 Unix 字符串吗?