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

我已经读过Stack Overflow,有些C函数是“过时的”或“应该避免”。 能不能给我一些这种function的例子和原因?

这些function有哪些替代方案?

我们可以安全地使用它们 – 任何良好的做法?

不推荐使用的function
不安全
这样一个函数的一个完美例子是gets() ,因为没有办法告诉它目标缓冲区有多大。 因此,任何使用gets()读取输入的程序都有一个缓冲区溢出漏洞 。 出于类似的原因,应该使用strncpy()代替strcpy()和strncat()来代替strcat() 。

然而,更多的例子包括tmpfile()和mktemp()函数,因为覆盖临时文件存在潜在的安全问题,并且被更安全的mkstemp()函数取代。

非重入
其他示例包括gethostbyaddr()和gethostbyname() ,它们是不可重入的(因此,不保证是线程安全的)并且已被重入的getaddrinfo()和freeaddrinfo()取代。

您可能在这里注意到一种模式……要么缺乏安全性(可能未能在签名中包含足够的信息以便可能安全地实现它),要么非重入是常见的弃用来源。

过时的,非便携式的
其他一些function只是被弃用,因为它们复制function并且不像其他变体那样可移植。 例如,不推荐使用bzero()而使用memset() 。

线程安全和重入
您在post中询问了线程安全和重入问题。 有一点点差异。 如果函数不使用任何共享的可变状态,则该函数是可重入的。 因此,例如,如果它需要的所有信息都传递给函数,并且所需的任何缓冲区也传递给函数(而不是由函数的所有调用共享),那么它是可重入的。 这意味着通过使用独立参数,不同的线程不会有意外共享状态的风险。 可重入是比线程安全更有力的保证。 如果函数可以由多个线程同时使用,则该函数是线程安全的。 如果以下情况,函数是线程安全

  • 它是可重入的(即它不会在调用之间共享任何状态),或者:
  • 它是不可重入的,但它根据共享状态的需要使用同步/锁定。

通常,在Single UNIX Specification和IEEE 1003.1 (即“POSIX”)中,任何不保证可重入的函数都不能保证是线程安全的。 因此,换句话说,只有保证可重入的函数可以在multithreading应用程序中可移植地使用(没有外部锁定)。 但是,这并不意味着这些标准的实现不能选择使非重入函数成为线程安全的。 例如,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()等没有问题,只要你自己制作格式字符串,就不会传递任意格式字符串或允许用户输入用作格式字符串,并在适当的位置使用中定义的格式化宏。 (注意,应该使用snprintf()代替sprintf() ,但这与未能指定目标缓冲区的大小而不使用格式字符串有关。 我还应该指出,在C ++中, boost :: format提供了类似printf的格式,没有varargs。

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

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

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

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

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

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

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

  • strncat()的length参数与(但不完全相同 – 参见第3点)相关,可以复制到目标的最大字符数而不是目标缓冲区的大小。 这使得strncat()比它应该更难使用,特别是如果多个项目将连接到目的地。
  • 可能很难确定结果是否被截断(可能重要也可能不重要)
  • 很容易出现一个错误的错误。 正如C99标准所指出的那样,“因此,对于看起来像strncat( s1, s2, n)的调用, s1指向的数组中可以结束的最大字符数是strlen(s1)+n+1

strncpy()也有一个问题,可能导致您尝试以直观的方式使用它的错误 – 它不保证目标是空终止。 要确保您必须通过自己在缓冲区的最后位置删除'\0'来确保专门处理该角落情况(至少在某些情况下)。

我建议使用类似OpenBSD的strlcat()strlcpy() (虽然我知道有些人不喜欢这些函数;我相信它们比strncat() / strncpy()更容易安全使用。

以下是Todd Miller和Theo de Raadt对strncat()strncpy()问题的一些看法:

strncpy()strncat()用作strcpy()strcat()安全版本时,会遇到几个问题。 这两个函数以不同的和非直观的方式处理NUL终止和长度参数,甚至使有经验的程序员感到困惑。 它们也没有提供检测何时发生截断的简单方法。 ……在所有这些问题中,由长度参数和相关的NUL终止问题引起的混淆是最重要的。 当我们审核OpenBSD源代码树以寻找潜在的安全漏洞时,我们发现了strncpy()strncat()滥用行为。 虽然并非所有这些都导致可利用的安全漏洞,但他们明确表示在安全字符串操作中使用strncpy()strncat()的规则被广泛误解。

OpenBSD的安全审计发现,具有这些function的错误“猖獗”。 与gets()不同,这些函数可以安全使用,但实际上存在很多问题,因为界面混乱,不直观且难以正确使用。 我知道微软也做了分析(虽然我不知道他们可能发布了多少数据),结果已被禁止(或至少非常强烈劝阻 – ‘禁令’可能不是绝对的)使用strncat()strncpy() (以及其他函数)。

一些链接包含更多信息:

有些人会声称应该避免使用strcpystrcat ,而是支持strncpystrncat 。 在我看来,这有点主观。

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

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

避免

  • multithreading程序的strtok不是线程安全的。
  • 因为它可能导致缓冲区溢出

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

SETJMP.H

  • setjmp() 。 与longjmp()一起,这些函数被广泛认为是非常危险的使用:它们导致意大利面条编程,它们带有多种forms的未定义行为,它们可能在程序环境中导致意外的副作用,例如影响存储在堆栈。 参考文献:MISRA-C:2012规则21.4, CERT C MSC22-C 。
  • longjmp() 。 请参阅setjmp()

stdio.h中

  • gets() 。 该function已从C语言中删除(根据C11),因为它根据设计不安全。 该function已在C99中标记为已废弃。 请改用fgets() 。 参考文献:ISO 9899:2011 K.3.5.4.1,另见注释404)。

stdlib.h中

  • atoi()函数系列。 它们没有error handling,但每当发生错误时都会调用未定义的行为。 完全多余的函数,可以用strtol()函数系列替换。 参考文献:MISRA-C:2012年规则21.7。

string.h中

  • strncat() 。 有一个经常被滥用的尴尬界面。 它主要是一个多余的function。 另请参阅strncpy()备注。
  • strncpy() 。 这个函数的目的永远不是一个更安全的strcpy()版本。 它的唯一目的始终是在Unix系统上处理一种古老的字符串格式,并且它包含在标准库中是一个已知的错误。 这个函数很危险,因为它可能会使字符串没有空终止,并且程序员经常会错误地使用它。 参考文献: 为什么strlcpy和strlcat被认为是不安全的? 。

标准库函数应谨慎使用:

ASSERT.H

  • assert() 。 带有开销,通常不应在生产代码中使用。 最好使用特定于应用程序的error handling程序,它显示错误但不一定关闭整个程序。

signal.h中

  • signal() 。 参考文献:MISRA-C:2012规则21.5, CERT C SIG32-C 。

STDARG.H

  • va_arg()函数族。 C程序中存在可变长度函数几乎总是表明程序设计不佳。 除非您有非常具体的要求,否则应该避免。

stdio.h中
通常, 不建议将整个库用于生产代码 ,因为它会出现许多行为定义不明确且类型安全性差的情况。

  • fflush() 。 非常适合用于输出流。 如果用于输入流,则调用未定义的行为。
  • gets_s() 。 安全版本的gets()包含在C11边界检查界面中。 根据C标准建议,最好使用fgets() 。 参考文献:ISO 9899:2011 K.3.5.4.1。
  • printf()函数系列。 资源繁重的function带来许多未定义的行为和类型安全性差。 sprintf()也有漏洞。 在生产代码中应避免使用这些function。 参考文献: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()不安全主要是由Microsoft创建的神话 。
  • strtok() 。 更改调用者字符串并使用内部状态变量,这可能使其在multithreading环境中不安全。

值得再次补充的是strncpy()不是strcpy()的通用替代品,而是它的名字可能暗示的。 它设计用于不需要nul-terminator的固定长度字段(它最初设计用于UNIX目录条目,但对加密密钥字段之类的东西很有用)。

但是,使用strncat()作为strcpy()的替代是很容易的:

 if (dest_size > 0) { dest[0] = '\0'; strncat(dest, source, dest_size - 1); } 

(在常见情况下,显然可以删除if测试,在那里你知道dest_size肯定是非零的)。

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

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

几乎所有处理NUL终止字符串的函数都可能不安全。 如果您正在接收来自外部世界的数据并通过str *()函数进行操作,那么您可以为灾难做好准备

不要忘记sprintf – 它是许多问题的原因。 这是正确的,因为替代方案snprintf有时会有不同的实现,这些实现可能会使代码无法移植。

  1. linux: http : //linux.die.net/man/3/snprintf

  2. windows: http : //msdn.microsoft.com/en-us/library/2ts7cx93%28VS.71%29.aspx

在case 1(linux)中,返回值是存储整个缓冲区所需的数据量(如果它小于给定缓冲区的大小,则输出被截断)

在情况2(窗口)中,如果输出被截断,则返回值是负数。

通常你应该避免不是的函数:

  1. 缓冲区溢出安全(这里已经提到了很多function)

  2. 线程安全/不可重入(例如strtok)

在每个函数的手册中,您应该搜索以下关键字:安全,同步,异步,线程,缓冲区,错误

安全地使用scanf非常困难。 很好地使用scanf可以避免缓冲区溢出,但是当读取不符合请求类型的数字时,您仍然容易受到未定义的行为的影响。 在大多数情况下, fgets随后进行自解析(使用sscanfstrchr等)是一个更好的选择。

但我不会说“一直避免scanf ”。 scanf有其用途。 举个例子,假设您想要读取长度为10个字节的char数组中的用户输入。 您想要删除尾随换行符(如果有)。 如果用户在换行前输入超过9个字符,则需要将前9个字符存储在缓冲区中并丢弃所有内容,直到下一个换行符为止。 你可以做:

 char buf[10]; scanf("%9[^\n]%*[^\n]", buf)); getchar(); 

一旦你习惯了这个成语,它就会更短,并且在某些方面比以下更清洁:

 char buf[10]; if (fgets(buf, sizeof buf, stdin) != NULL) { char *nl; if ((nl = strrchr(buf, '\n')) == NULL) { int c; while ((c = getchar()) != EOF && c != '\n') { ; } } else { *nl = 0; } } 

在所有的字符串复制/移动场景中 – strcat(),strncat(),strcpy(),strncpy()等等 – 如果强制执行几个简单的启发式方法,情况会好得多( 更安全 ):

1.在添加数据之前,始终NUL填充缓冲区。
2.使用宏常量将字符缓冲区声明为[SIZE + 1]。

例如,给定:

 #define BUFSIZE 10 char Buffer[BUFSIZE+1] = { 0x00 }; /* The compiler NUL-fills the rest */ 

我们可以使用如下代码:

 memset(Buffer,0x00,sizeof(Buffer)); strncpy(Buffer,BUFSIZE,"12345678901234567890"); 

相对安全。 memset()应该出现在strncpy()之前,即使我们在编译时初始化了Buffer,因为在调用函数之前我们不知道其他代码放入了什么垃圾。 strncpy()会将复制的数据截断为“1234567890”,并且不会对其进行 NUL终止。 但是,因为我们已经NUL填充了整个缓冲区 – sizeof(缓冲区)而不是BUFSIZE – 所以只要我们使用BUFSIZE约束我们的写入,就可以保证终止NUL的最终“超出范围”。常量,而不是sizeof(缓冲区)。

缓冲区和BUFSIZE同样适用于snprintf():

 memset(Buffer,0x00,sizeof(Buffer)); if(snprintf(Buffer,BUFIZE,"Data: %s","Too much data") > BUFSIZE) { /* Do some error-handling */ } /* If using MFC, you need if(... < 0), instead */ 

即使snprintf()专门写入BUFIZE-1字符,然后是NUL,这也可以安全地工作。 所以我们在Buffer的末尾“浪费”一个无关的NUL字节...我们防止缓冲区溢出和未终止的字符串条件,因为内存成本非常小。

我对strcat()和strncat()的调用更加强硬:不要使用它们。 安全地使用strcat()很困难,并且strncat()的API非常直观,以至于正确使用它所需的努力会抵消任何好处。 我提议以下内容:

 #define strncat(target,source,bufsize) snprintf(target,source,"%s%s",target,source) 

创建一个strcat()插件很诱人,但不是一个好主意:

 #define strcat(target,source) snprintf(target,sizeof(target),"%s%s",target,source) 

因为target可能是一个指针(因此sizeof()不会返回我们需要的信息)。 我对代码中的strcat()实例没有一个好的“通用”解决方案。

我经常从“strFunc() - 意识到”程序员遇到的问题是尝试通过使用strlen()来防止缓冲区溢出。 如果保证内容被NUL终止,这很好。 否则,在您遇到要保护的“有问题”代码之前,strlen()本身可能会导致缓冲区溢出错误(通常会导致分段违规或其他核心转储情况)。

atoi不是线程安全的。 我根据手册页的建议使用strtol。