标准库有哪些function必须/应该避免?

我读过堆栈溢出一些C函数是“过时”或“应该避免”。 请给我举一些这样的function和原因的例子吗?

这些function有哪些替代scheme?

我们可以安全地使用它们吗?

弃用函数
不安全
这个函数的一个很好的例子是gets() ,因为没有办法告诉它目的缓冲区有多大。 因此,任何使用gets()读取input的程序都有一个缓冲区溢出漏洞 。 由于类似的原因,应该使用strncpy()代替strcat()和strncat()代替strcat() 。

还有一些例子包括tmpfile()和mktemp()函数,这是由于覆盖临时文件的潜在安全问题,并且被更安全的mkstemp()函数所取代。

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

您可能会注意到这里的一个模式…或者缺乏安全性(可能是因为没有在签名中包含足够的信息来安全地实现它)或不可再生是常见的贬值来源。

过时的,不便携的
一些其他function简单地被弃用,因为它们重复function并且不像其他变体那样便携。 例如, bzero()已被弃用,以支持memset() 。

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

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

一般来说,在单一UNIX规范和IEEE 1003.1 (即“POSIX”)中,不保证任何不能重入的函数都是线程安全的。 所以,换句话说,只有保证可重入的函数才能被移植到multithreading应用程序中(无需外部locking)。 但是,这并不意味着这些标准的实现不能select使非重入函数线程安全。 例如,Linux经常向不可重入函数添加同步,以便为threadsafety添加一个保证(超出了单一UNIX规范的保证)。

string(和内存缓冲区,一般)
你还问到了string/数组是否存在一些根本的缺陷。 有些人可能会认为是这样,但我认为不是,这个语言没有根本的缺陷。 C和C ++要求您分别传递一个数组的长度/容量(不像其他一些语言中那样是“.length”属性)。 这本身并不是一个缺陷。 任何C和C ++开发人员都可以根据需要简单地将长度作为参数来编写正确的代码。 问题是需要这些信息的几个API没有把它指定为一个参数。 或者假设使用一些MAX_BUFFER_SIZE常量。 现在这样的API已被弃用,并被允许指定数组/缓冲区/string大小的替代API取代。

Scanf(回答您的最后一个问题)
就我个人而言,我使用C ++ iostreams库(std :: cin,std :: cout,<<和>>运算符,std :: getline,std :: istringstream,std :: ostringstream等),所以我不通常处理这一点。 如果我不得不使用纯C,我个人只需要使用fgetc()或getchar()与strtol() , strtoul()等结合使用,并手动parsing,因为我不是可变参数或格式string。 这就是说,据我所知,只要你自己制作格式化string,你就不会传递任意格式的string,也不允许用户使用[f] scanf() , [f] printfinput用作格式string,并在适当的地方使用<inttypes.h>中定义的格式化macros。 (注意,应该使用snprintf()来代替sprintf() ,但是这与无法指定目标缓冲区的大小以及不使用格式string有关。 我还应该指出,在C ++中, boost :: format提供了不带可变参数的类似printf的格式。

人们又一次重复着,口头禅般地说,“n”版本的strfunction是安全的。

如果这是他们的目标,那么他们总是会终止string。

这些函数的“n”版本是为固定长度的字段(例如早期文件系统中的目录项)而编写的,其中只有在string不填充字段时才需要nul终止符。 这也是为什么这些函数具有奇怪的副作用,如果只是作为replace使用,效率低下 – 以strncpy()为例:

如果由s2指向的数组是一个比n个字节短的string,则空字节会被附加到s1所指向的数组的副本中,直到写入全部的n个字节为止。

由于缓冲区分配处理文件名通常是4KB,这可能会导致性能的大幅恶化。

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

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

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

strncpy()也有一个问题,可能会导致错误,您尝试以直观的方式使用它 – 它不能保证目的地是null终止。 为了确保你必须确保你特别处理那个angular落的情况下,通过在缓冲区的最后一个位置丢掉一个'\0' (至less在某些情况下)。

我build议使用OpenBSD的strlcat()strlcpy() (尽pipe我知道有些人不喜欢这些函数;我相信它们比strncat() / strncpy() )更安全。

以下是关于strncat()strncpy()一些问题,Todd Miller和Theo de Raadt对此有何评论:

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

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

一些链接更多的信息:

避免

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

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

在处理用户input时绝对应该避免 – 毫无疑问在这里。

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

几乎所有处理NUL终止string的函数都可能是不安全的。 如果您从外部接收数据并通过str *()函数操作,那么您将自己设置为灾难

strncpy()不是strcpy()的通用替代品,它可能是build议的。 它是为不需要终结符的固定长度字段而devise的(最初devise用于UNIX目录条目,但可用于诸如encryption密钥字段之类的内容)。

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

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

if你知道dest_size肯定是非零的话, iftesting显然可以放弃。

不要忘记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

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

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

一般来说,你应该避免不是的function:

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

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

在每个函数的手册中,您应该search关键字如:安全,同步,asynchronous,线程,缓冲区,错误

另外检查一下微软禁止使用的API列表。 这些API(包括许多已经列在这里的)被禁止使用微软代码,因为它们经常被滥用,并导致安全问题。

你可能不同意所有这些,但是他们都值得考虑。 当它的滥用导致了一些安全漏洞时,他们将API添加到列表中。

应该使用的标准库函数:

SETJMP.H

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

stdio.h中

  • gets() 。 该function已从C语言中删除(按照C11),因为按devise它是不安全的。 该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系统上总是处理一个古老的string格式,而它被包含在标准库中是一个已知的错误。 这个函数是危险的,因为它可能会使string没有空终止,并且程序员经常使用它不正确。 参考文献: 为什么strlcpy和strlcat被认为是不安全的? 。

标准库函数应谨慎使用:

ASSERT.H

  • assert() 。 带有开销,一般不应用于生产代码。 最好使用特定于应用程序的error handling程序来显示错误,但不一定closures整个程序。

signal.h中

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

STDARG.H

  • va_arg()函数族。 C程序中变长函数的存在几乎总是表示程序devise不佳。 应该避免,除非你有非常具体的要求。

stdio.h中
一般来说, 整个库不build议用于生产代码 ,因为它有很多不明确的行为和不良types安全的情况。

  • fflush() 。 完美的罚款用于输出stream。 如果用于inputstream,则调用未定义的行为。
  • gets_s() 。 C11边界检查界面中包含gets()安全版本。 根据C标准build议,最好使用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()函数族。 在托pipe系统中使用完美无瑕,但请注意C90中的众所周知的问题,因此不会产生结果 。 malloc()系列函数决不能在独立应用程序中使用。 参考文献:MISRA-C:2012规则21.3。

    还要注意,如果用realloc()的结果覆盖旧的指针, realloc()是危险的。 如果该function失败,你创build一个泄漏。

  • system() 。 有很多的开销,虽然可移植,但通常使用系统特定的API函数更好。 带有各种不明确的行为。 参考文献: CERT C ENV33-C 。

string.h中

  • strcat() 。 请参阅strcpy()注释。
  • strcpy() 。 除非要复制的数据的大小未知或大于目标缓冲区,否则完全可以使用。 如果没有检查传入的数据大小,可能会出现缓冲区溢出。 这不是strcpy()本身的问题,而是调用应用程序的问题 – strcpy()是不安全的,大多是微软创造的一个神话 。
  • strtok() 。 修改调用者string并使用内部状态variables,这可能会使其在multithreading环境中变得不安全。

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

但是我不会说“总是避免scanf ”。 scanf有它的用途。 作为一个例子,假设您想要读取长度为10个字节的char数组中的用户input。 你想删除尾随的换行符,如果有的话。 如果用户在换行符之前input了多于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; } } 

在所有的string复制/移动场景中,strcat(),strncat(),strcpy(),strncpy()等等 – 如果强制执行几个简单的启发式,事情会变得更好( 更安全

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

例如,给出:

#define BUFSIZE 10
char Buffer [BUFSIZE + 1] = {0x00}; / *编译器NUL填充其余* /

我们可以使用如下代码:

memset的(缓冲液,0×00,的sizeof(缓冲液));
函数strncpy(缓冲液,BUFSIZE, “12345678901234567890”);

相对安全。 即使我们在编译时初始化了Buffer,memset()也应该出现在strncpy()之前,因为我们不知道在我们的函数被调用之前,其他的代码被放到了什么地方。 strncpy()会将复制的数据截断为“1234567890”,而不会 NUL终止它。 但是,由于我们已经用NUL填充了整个buffer-sizeof(Buffer),而不是BUFSIZE,所以只要我们使用BUFSIZE来限制我们的写操作,保证会有一个最终的“超出范围”的终止NUL常量,而不是sizeof(Buffer)。

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

memset的(缓冲液,0×00,的sizeof(缓冲液));

if(snprintf(Buffer,BUFIZE,“Data:%s”,“Too much data”)> BUFSIZE){
/ *做一些error handling/
} /如果使用MFC,你需要if(… <0),而不是* /

尽pipesnprintf()特别写了BUFIZE-1字符,然后是NUL,但是这个工作是安全的。 所以我们在Buffer的末尾“浪费”了一个无关的NUL字节……我们防止了缓冲区溢出和未终止的string条件,只花费相当小的内存成本。

我对strcat()和strncat()的调用更强硬:不要使用它们。 安全地使用strcat()是困难的,而strncat()的API是非常直观的,所以使用它的努力正确地否定了任何好处。 我提出以下的build议:

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

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

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

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

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

所有具有n版本的函数应该被避免。 例如,使用strncpy而不是strcpy。

atoi不是线程安全的。 我使用strtol,而不是从手册页推荐。