必须(应该)避免使用标准库中的哪些功能?

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 SpecificationIEEE 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.

  • 通常也应该避免使用`strncpy`.它并没有像大多数程序员所认为的那样做.它不保证终止(导致缓冲区溢出),并且它填充较短的字符串(在某些情况下可能会降低性能). (11认同)
  • "Deprecated"是一个强有力的词,在讨论C++标准时具有特定的含义.从这个意义上讲,不推荐使用gets(),strcpy()等. (4认同)
  • 只要您区分"被C标准弃用","被Michael Aaron Safyan弃用",以及"被不知名的人所推崇,他们希望知道他们在谈论什么[引证需要]".问题*是*关于首选编码风格,而不是关于C标准,所以后两个是合适的.但是像Neil一样,在意识到你的陈述并不意味着暗示第一个含义之前,我需要双重考虑. (4认同)
  • 这个答案传播了无意义的教条,即"用strncpy替换strcpy - 我不知道为什么,但微软告诉我." strncpy从来没有打算成为strcpy的安全版本!面对它更不安全.请参阅[为什么strlcpy和strlcat被认为是不安全的?](/sf/ask/148042751/). (3认同)
  • @Adrian:我同意你的观点 - 既不是'strncpy()`也不是更糟糕的`strncat()`是n-less变体的合理替代品. (2认同)

Dip*_*ick 23

人们再一次重复,像咒语一样,荒谬的断言,"n"版本的str函数是安全版本.

如果这是他们的意图,那么他们总是会终止字符串.

这些函数的"n"版本被编写用于固定长度字段(例如早期文件系统中的目录条目),其中仅当字符串未填充字段时才需要nul终止符.这也是为什么函数具有奇怪的副作用的原因,如果仅用作替换,这些副作用毫无效率 - 例如采用strncpy():

如果s2指向的数组是一个短于n个字节的字符串,则将空字节附加到s1指向的数组中的副本,直到写入所有n个字节.

由于分配用于处理文件名的缓冲区通常为4千字节,因此可能导致性能大幅下降.

如果你想要"所谓的"安全版本然后获得 - 或编写你自己的 - strl例程(strlcpy,strlcat等),它总是nul终止字符串并且没有副作用.请注意,虽然这些并不是非常安全,因为它们可以默默地截断字符串 - 这在任何真实世界的程序中都不是最好的行动方案.在某些情况下这是可以的,但也有许多情况可能会导致灾难性后果(例如打印出医疗处方).

  • 你对“strncpy()”的看法是正确的,但对“strncat()”的看法是错误的。`strncat()` 并非设计用于固定长度字段 - 它实际上被设计为限制连接字符数的 `strcat()`。通过在进行多个串联时跟踪缓冲区中剩余的空间,可以很容易地将其用作“安全的`strcat()`”,甚至更容易将其用作“安全的`strcpy()`”(通过在调用之前将目标缓冲区的第一个字符设置为“\0”)。`strncat()` *总是*终止目标字符串,并且它不会写入额外的`'\0'`。 (2认同)
  • @caf - 是的但strncat()完全没用,因为它将要复制的最大长度作为参数 - 而不是目标缓冲区的长度.为了防止缓冲区溢出,你需要知道当前目标字符串的长度,如果你知道为什么你会使用strncat() - 它必须再次处理dest长度 - 而不仅仅是strlcat()源字符串到dest字符串的结尾. (2认同)
  • @chrisharris:`strncat()`无论源字符串的长度如何都能正常工作,而`strcat()`则不会.这里`strlcat()`的问题在于它不是标准的C函数. (2认同)

Mic*_*urr 19

几个答案在这里建议使用strncat()strcat(); 我建议也应该避免strncat()(和strncpy()).它有一些问题,使其难以正确使用并导致错误:

  • length参数to strncat()与(但不完全相同 - 参见第3点)可以复制到目标的最大字符数而不是目标缓冲区的大小相关.这strncat()比使用起来更难以使用,特别是如果多个项目将连接到目的地.
  • 可能很难确定结果是否被截断(可能重要也可能不重要)
  • 很容易出现一个错误.正如C99标准所指出的那样,"因此,可以在数组中指向的最大字符数s1strlen(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()(以及其他功能).

一些链接提供更多信息:

  • caf和David大约100%对我的主张是'strncat()`并不总是null终止.我混淆了`strncat()`和`strncpy()`的行为(他们的功能要避免的另一个原因 - 他们的名字意味着类似的行为,但实际上在重要方面表现不同......).我修改了我的答案以纠正这个问题并添加其他信息. (2认同)

Eli*_*sky 7

有些人会声称strcpy并且strcat应该避免,赞成strncpystrncat.在我看来,这有点主观.

在处理用户输入时,绝对应该避免使用它们 - 毫无疑问.

在远离用户的代码中,当你只知道缓冲区足够长时,strcpy并且strcat可能会更高效,因为计算n传递给它们的堂兄弟可能是多余的.

  • 如果可用的话,更好的是`strlcat`和`strlcpy`,因为'n'版本不保证目标字符串的NULL终止. (4认同)

Lun*_*din 7

永远不应使用的标准库函数:

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中

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().更改调用者字符串并使用内部状态变量,这可能使其在多线程环境中不安全.

  • 在函数 A 中使用 `strtok()` 意味着 (a) 该函数不能在 A 使用它时调用任何其他也使用 `strtok()` 的函数,并且 (b) 意味着没有调用 A 的函数可以使用`strtok()` 当它调用 A 时。换句话说,使用 `strtok()` 会使调用链中毒;它不能在库代码中安全使用,因为它必须记录它使用 `strtok()` 来防止 `strtok()` 的其他用户调用库代码。 (2认同)

cod*_*ict 6

避免

  • strtok 对于多线程程序而言,它不是线程安全的.
  • gets 因为它可能导致缓冲区溢出

  • `strtok()`的问题远远超出了线程的安全性 - 即使在单线程程序中它也不安全,除非您确定在使用`strtok()时代码可能没有调用的函数`不要使用它(或者他们会从你的下方弄乱'strtok()'的状态.事实上,大多数针对多线程平台的编译器都会处理`strtok()`的潜在问题,就线程来说,使用strtok()的静态数据进行线程局部存储.但是,当你(在同一个线程中)时,仍然无法解决使用它的其他函数的问题. (9认同)
  • 它们的情况略有不同.如果您知道您的程序不是多线程的,或者您以某种方式锁定对它的访问权限,那么您可以安全地使用strtok,但是您不能安全地使用它,所以永远不要使用它. (2认同)

caf*_*caf 5

可能值得再次添加,这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绝对是非零的).


Adr*_*thy 5

还要查看Microsoft的禁用API列表。这些API(包括此处已列出的许多API)已被Microsoft代码禁止,因为它们经常被滥用并导致安全问题。

您可能不同意所有这些,但都值得考虑。当误用导致许多安全漏洞时,他们会将API添加到列表中。

  • 但请注意,推荐的替换功能几乎都是 Microsoft 特定的。如果您遵循这一点,您将有效地使您的代码不可移植且仅限 Microsoft。[不,C 的附件 K 函数的 Microsoft 实现不可移植:](http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1967.htm#impementations):“Microsoft Visual Studio 实现了 API 的早期版本。但是,实现并不完整,既不符合 C11,也不符合原始 TR 24731-1。...” (2认同)
  • (续)“由于与规范存在大量偏差,因此 Microsoft 的实现不能被认为是合规或可移植的。” (2认同)