And*_*anu 87 c standard-library obsolete
我已经读过Stack Overflow,有些C函数是"过时的"或"应该避免".你能告诉我一些这种功能的例子和原因吗?
这些功能有哪些替代方案?
我们可以安全地使用它们 - 任何好的做法?
Mic*_*yan 56
不推荐使用的函数
不安全
这种函数的一个完美示例是gets(),因为无法告诉它目标缓冲区有多大.因此,任何使用gets()读取输入的程序都有一个缓冲区溢出漏洞.出于类似的原因,应该使用strncpy()代替strcpy()和strncat()来代替strcat().
然而,更多的例子包括tmpfile()和mktemp()函数,因为覆盖临时文件存在潜在的安全问题,并且被更安全的mkstemp()函数所取代.
非重入
其他示例包括gethostbyaddr()和gethostbyname(),它们是不可重入的(因此,不保证是线程安全的)并且已被重入的getaddrinfo()和freeaddrinfo()取代.
您可能在这里注意到一种模式......要么缺乏安全性(可能未能在签名中包含足够的信息以便可能安全地实施它),要么非重入是常见的弃用来源.
过时的,非便携式的
一些其他功能只是被弃用,因为它们复制功能并且不像其他变体那样可移植.例如,不推荐使用bzero()而使用memset().
线程安全和重入
您在帖子中询问了线程安全和重入.有一点点差异.如果函数不使用任何共享的可变状态,则该函数是可重入的.因此,例如,如果它需要的所有信息都传递给函数,并且所需的任何缓冲区也传递给函数(而不是由函数的所有调用共享),那么它是可重入的.这意味着通过使用独立参数,不同的线程不会意外地共享状态.重入是比线程安全更有力的保证.如果函数可以同时由多个线程使用,则该函数是线程安全的.如果以下情况,函数是线程安全
通常,在Single UNIX Specification和IEEE 1003.1(即"POSIX")中,任何不保证可重入的函数都不能保证是线程安全的.因此,换句话说,只有保证可重入的函数可以在多线程应用程序中可移植地使用(没有外部锁定).但是,这并不意味着这些标准的实现不能选择使非重入函数成为线程安全的.例如,Linux经常向非重入函数添加同步,以便添加线程安全的保证(超出单一UNIX规范的保证).
字符串(和内存缓冲区,一般)
您还询问是否存在字符串/数组的一些基本缺陷.有些人可能会说这是事实,但我认为不,这种语言没有根本性的缺陷.C和C++要求您分别传递数组的长度/容量(它不像某些其他语言那样是".length"属性).这本身并不是一个缺陷.任何C和C++开发人员只需将长度作为参数传递给需要的人就可以编写正确的代码.问题是需要此信息的几个API未能将其指定为参数.或者假设将使用一些MAX_BUFFER_SIZE常量.此类API现在已被弃用,并替换为允许指定数组/缓冲区/字符串大小的替代API.
Scanf(回答你的上一个问题)
我个人使用C++ iostreams库(std :: cin,std :: cout,<<和>>运算符,std :: getline,std :: istringstream,std :: ostringstream等等),所以我通常不会处理这个问题.但是,如果我被迫使用纯C,我个人只会将fgetc()或getchar()与strtol(),strtoul()等结合使用并手动解析,因为我不是varargs或格式字符串.也就是说,据我所知,[f] scanf(),[f] printf()等没有问题,只要你自己制作格式字符串,就不会传递任意格式字符串或允许用户输入用作格式字符串,并在适当的位置使用<inttypes.h>中定义的格式化宏.(注意,应该使用snprintf()代替sprintf(),但这与未能指定目标缓冲区的大小而不是格式字符串的使用有关.我还应该指出,在C++中,boost :: format提供了类似printf的格式,没有varargs.
Dip*_*ick 23
人们再一次重复,像咒语一样,荒谬的断言,"n"版本的str函数是安全版本.
如果这是他们的意图,那么他们总是会终止字符串.
这些函数的"n"版本被编写用于固定长度字段(例如早期文件系统中的目录条目),其中仅当字符串未填充字段时才需要nul终止符.这也是为什么函数具有奇怪的副作用的原因,如果仅用作替换,这些副作用毫无效率 - 例如采用strncpy():
如果s2指向的数组是一个短于n个字节的字符串,则将空字节附加到s1指向的数组中的副本,直到写入所有n个字节.
由于分配用于处理文件名的缓冲区通常为4千字节,因此可能导致性能大幅下降.
如果你想要"所谓的"安全版本然后获得 - 或编写你自己的 - strl例程(strlcpy,strlcat等),它总是nul终止字符串并且没有副作用.请注意,虽然这些并不是非常安全,因为它们可以默默地截断字符串 - 这在任何真实世界的程序中都不是最好的行动方案.在某些情况下这是可以的,但也有许多情况可能会导致灾难性后果(例如打印出医疗处方).
Mic*_*urr 19
几个答案在这里建议使用strncat()
过strcat()
; 我建议也应该避免strncat()
(和strncpy()
).它有一些问题,使其难以正确使用并导致错误:
strncat()
与(但不完全相同 - 参见第3点)可以复制到目标的最大字符数而不是目标缓冲区的大小相关.这strncat()
比使用起来更难以使用,特别是如果多个项目将连接到目的地.s1
是strlen(s1)+n+1
",对于看起来像strncat( s1, s2, n)
strncpy()
还有一个问题,可能导致您尝试以直观的方式使用它的错误 - 它不保证目标是空终止.确保您必须通过'\0'
自己将缓冲区的最后位置放入缓冲区(至少在某些情况下)来确保专门处理该角落情况.
我建议使用类似OpenBSD的strlcat()
和strlcpy()
(虽然我知道有些人不喜欢这样的功能,我相信他们更容易安全地使用比strncat()
/ strncpy()
).
以下是对托德·米勒和西奥德若特不得不说的有问题的一个小strncat()
和strncpy()
:
还有时遇到的几个问题
strncpy()
和strncat()
被用作安全版本strcpy()
和strcat()
.这两个函数以不同的和非直观的方式处理NUL终止和长度参数,甚至使有经验的程序员感到困惑.它们也没有提供检测何时发生截断的简单方法.......在所有这些问题中,由长度参数和相关的NUL终止问题引起的混乱是最重要的.当我们审核OpenBSD源代码树中潜在的安全漏洞时,我们发现了滥用strncpy()
和strncat()
.虽然并非所有这些都导致可利用的安全漏洞,但他们明确表示使用strncpy()
和strncat()
安全字符串操作的规则被广泛误解.
OpenBSD的安全审计发现,这些功能的漏洞"猖獗".不同的是gets()
,这些功能可以安全使用,但实际上存在很多问题,因为界面混乱,不直观且难以正确使用.我知道微软也做了分析(虽然我不知道他们可能发布了多少数据),结果已被禁止(或至少非常强烈劝阻 - '禁令'可能不是绝对的)使用strncat()
和strncpy()
(以及其他功能).
一些链接提供更多信息:
有些人会声称strcpy
并且strcat
应该避免,赞成strncpy
和strncat
.在我看来,这有点主观.
在处理用户输入时,绝对应该避免使用它们 - 毫无疑问.
在远离用户的代码中,当你只知道缓冲区足够长时,strcpy
并且strcat
可能会更高效,因为计算n
传递给它们的堂兄弟可能是多余的.
SETJMP.H
setjmp()
.再加上longjmp()
,这些功能被广泛recogniced为极端危险的地方使用:它们会导致意大利面条编程,他们提出的未定义行为多种形式,它们可能会导致程序环境意想不到的副作用,如影响存储在堆栈上的值.参考文献:MISRA-C:2012规则21.4,CERT C MSC22-C. longjmp()
.见setjmp()
.stdio.h中
gets()
.该功能已从C语言中删除(根据C11),因为它根据设计不安全.该功能已在C99中标记为已过时.请fgets()
改用.参考文献:ISO 9899:2011 K.3.5.4.1,另见注释404).stdlib.h中
atoi()
一系列功能.它们没有错误处理,但每当发生错误时都会调用未定义的行为.完全多余的功能,可以用strtol()
功能系列替换.参考文献:MISRA-C:2012年规则21.7.string.h中
strncat()
.有一个经常被滥用的尴尬界面.它主要是一个多余的功能.另见备注strncpy()
.strncpy()
.这个功能的目的永远不是一个更安全的版本strcpy()
.它的唯一目的始终是在Unix系统上处理一种古老的字符串格式,并且它包含在标准库中是一个已知的错误.此函数很危险,因为它可能会使字符串没有空终止,并且程序员通常会错误地使用它.参考文献:为什么strlcpy和strlcat被认为是不安全的?.ASSERT.H
assert()
.带有开销,通常不应在生产代码中使用.最好使用特定于应用程序的错误处理程序,它显示错误但不一定关闭整个程序.signal.h中
signal()
.参考文献:MISRA-C:2012规则21.5,CERT C SIG32-C.STDARG.H
va_arg()
一系列功能.C程序中存在可变长度函数几乎总是表明程序设计不佳.除非您有非常具体的要求,否则应该避免.stdio.h
一般来说,不建议将整个库用于生产代码,因为它会出现许多行为定义不明确且类型安全性差的案例.
fflush()
.非常适合用于输出流.如果用于输入流,则调用未定义的行为.gets_s()
.gets()
C11边界检查界面中包含的安全版本.fgets()
根据C标准推荐,优选使用.参考文献:ISO 9899:2011 K.3.5.4.1.printf()
一系列功能.资源繁重的功能带来许多未定义的行为和类型安全性差.sprintf()
也有漏洞.在生产代码中应避免使用这些功能.参考文献:MISRA-C:2012年规则21.6. scanf()
一系列功能.见评论printf()
.此外, - scanf()
如果未正确使用,则容易受到缓冲区溢出的影响.fgets()
在可能的情况下优选使用.参考文献:CERT C INT05-C,MISRA-C:2012年规则21.6. tmpfile()
一系列功能.附带各种漏洞问题.参考文献:CERT C FIO21-C.stdlib.h中
malloc()
一系列功能.完全可以在托管系统中使用,但要注意C90中的众所周知的问题,因此不会产生结果.该malloc()
系列功能绝不应用于独立应用.参考文献:MISRA-C:2012年规则21.3.
另请注意,realloc()
如果您使用结果覆盖旧指针,则会很危险realloc()
.如果函数失败,则会创建泄漏.
system()
.有很多开销,虽然是可移植的,但通常最好使用特定于系统的API函数.伴随着各种定义不明确的行为.参考文献:CERT C ENV33-C.string.h中
strcat()
.请参阅备注strcpy()
.strcpy()
.除非要复制的数据大小未知或大于目标缓冲区,否则完全可以使用.如果未检查传入数据大小,则可能存在缓冲区溢出.这strcpy()
本身并不是错误,而是调用应用程序 - strcpy()
不安全主要是微软创建的神话.strtok()
.更改调用者字符串并使用内部状态变量,这可能使其在多线程环境中不安全.避免
strtok
对于多线程程序而言,它不是线程安全的.gets
因为它可能导致缓冲区溢出可能值得再次添加,这strncpy()
不是strcpy()
它的名称可能暗示的通用替代品.它适用于不需要nul-terminator的固定长度字段(它最初设计用于UNIX目录条目,但对加密密钥字段之类的东西很有用).
但是,很容易用它strncat()
作为替代品strcpy()
:
if (dest_size > 0)
{
dest[0] = '\0';
strncat(dest, source, dest_size - 1);
}
Run Code Online (Sandbox Code Playgroud)
(if
在常见情况下,显然可以放弃测试,你知道这dest_size
绝对是非零的).
还要查看Microsoft的禁用API列表。这些API(包括此处已列出的许多API)已被Microsoft代码禁止,因为它们经常被滥用并导致安全问题。
您可能不同意所有这些,但都值得考虑。当误用导致许多安全漏洞时,他们会将API添加到列表中。