C标准库中的哪些函数通常会鼓励不好的练习?

这是由这个问题和一个特定答案的意见,我得知, strncpy不是一个非常安全的string处理函数在C中,它填充零,直到达到n ,我没有意识到的东西。

具体来说,引用R ..

strncpy不会空终止,并且会将目标缓冲区的整个剩余部分填零,这是非常浪费时间的。 你可以通过添加你自己的null填充来解决前者,而不是后者。 它从未打算用作“安全string处理”function,而是用于在Unix目录表和数据库文件中使用固定大小的字段。 snprintf(dest,n,“%s”,src)是标准C中唯一正确的“安全strcpy”,但它可能会慢很多。 顺便说一句,截断本身可能是一个重大的bug,在某些情况下可能会导致特权提升或DoS,所以抛出“安全”的string函数,以截断他们的输出在一个问题不是一种方式,使其“安全”或“安全”。 相反,你应该确保目标缓冲区是正确的大小,并简单地使用strcpy(或更好,如果你已经知道源string的长度,memcpy)。

从Jonathan Leffler

请注意,strncat()在界面上比strncpy()更令人困惑 – 这个长度参数到底是什么呢? 根据你提供的strncpy()等,这不是你所期望的,所以它比strncpy()更容易出错。 对于复制string,我越来越认为有一个强有力的论点,你只需要memmove(),因为你总是提前知道所有的大小,并确保提前有足够的空间。 使用memmove()优先于strcpy(),strcat(),strncpy(),strncat(),memcpy()中的任何一个。

所以,我在C标准库上显然有点生疏。 因此,我想提出一个问题:

哪些C标准库函数被不恰当地/以可能导致/导致安全问题/代码缺陷/低效率的方式使用?

为了客观,我有一些答案的标准:

  • 请,如果可以的话,引用背后的functiondevise理由,即其预期的目的。
  • 请突出显示代码当前被滥用的情况。
  • 请说明为什么这种滥用会导致问题。 我知道这应该是显而易见的,但它防止软性答案。

请避免:

  • 关于命名职能惯例的争论(除非这明确导致混淆)。
  • “我更喜欢x而y” – 偏好没问题,我们都有,但是我对实际意想不到的副作用感兴趣,以及如何防范。

由于这可能被认为是主观的,并没有明确的答案,我马上标记为社区维基。

我也按C99工作。

strtok()函数的一个常见错误是假设parsing的string保持不变,而实际上用'\0'代替分隔符。

而且,通过对其进行后续调用来使用strtok() ,直到整个string被标记为止。 某些库实现将strtok()的内部状态存储在全局variables中,如果同时从多个线程调用strtok() ,则可能会引发一些令人不快的惊喜。

CERT C安全编码标准列出了您询问的许多这些缺陷。

哪些C标准库函数被不恰当地/以可能导致/导致安全问题/代码缺陷/低效率的方式使用?

我要去与明显的:

 char *gets(char *s); 

由于其显着的特殊性,恰当地使用它是根本不可能的。

在几乎所有情况下,不应使用atoi() (这也适用于atof()atol()atoll() )。

这是因为这些函数根本没有检测到超出范围的错误 – 标准只是说“如果结果的值不能被表示,行为是不确定的”。 。 所以唯一可以安全使用的是如果你能够certificateinput肯定在范围之内(例如,如果你传递一个长度为4或更小的string给atoi() ,它不能超出范围)。

而是使用strtol()系列的函数之一。

让我们把问题扩展到广义上的接口。

errno

从技术上来说,它甚至不清楚它是什么,一个variables,一个macros,一个隐含的函数调用? 在现代系统的实践中,它大部分是一个macros转换成一个函数调用来具有线程特定的错误状态。 这是邪恶的:

  • 因为它可能会导致调用者访问该值的开销,检查“错误”(这可能只是一个例外事件)
  • 因为它甚至在某些地方强加一个库调用之前,调用者清除这个“variables”
  • 因为它通过设置库的全局状态来实现简单的错误返回。

即将出台的标准使得errno的定义更加直观,但这些uglines仍然存在

经常有一个strtok_r。

对于realloc,如果你需要使用旧的指针,使用其他variables并不难。 如果程序因分配错误而失败,那么清理旧指针通常不是必须的。

我会把printfscanf放在这个清单上。 事实上,你必须使格式化说明符完全正确,这使得这些函数使用起来非常棘手,而且很容易出错。 读取数据时,也很难避免缓冲区溢出。 而且,当好心的程序员指定客户端指定的string作为printf的第一个参数时,“printf格式string漏洞”可能会造成无数的安全漏洞,只能发现堆栈被捣毁,安全性受到影响。

任何操作全局状态的函数,如gmtime()localtime() 。 这些function不能在多个线程中安全使用。

编辑: rand()在看起来在同一类别。 至less没有线程安全的保证,在我的Linux系统上,手册页警告说它是不可重入的并且不是线程安全的。

我的黑客之一是strtok() ,因为它是不可重入的,并且因为它将string处理成碎片,所以在它所分离的每个令牌的末尾插入NUL。 这个问题是军团的; 令人痛心的是经常被吹捧为一个问题的解决scheme,但是它本身也经常是一个问题。 并不总是 – 它可以安全地使用。 但是,只有当你小心。 大多数函数也是如此,除了gets()这个不能安全使用的值得注意的例外。

关于realloc已经有一个答案,但我有不同的看法。 很多时候,我看到有些人写的是freemalloc – 换句话说,当他们有一个缓冲区充满垃圾,需要改变大小之前,存储新的数据。 这当然会导致潜在的巨大caching抖动垃圾堆memcpy即将被覆盖。

如果正确使用增长的数据(避免最坏情况下的O(n^2)性能将对象扩大到n ,即在空间不足的情况下增加缓冲区的几何形状而不是线性), realloc会带来可疑的好处简单地做你自己的新的mallocmemcpyfree cycle。 realloc唯一的方法可以避免在内部做这个事情,当你使用堆顶部的单个对象的时候。

如果你喜欢用calloc填充新的对象,很容易忘记realloc不会填满新的部分。

最后, realloc一个更常见的用法是分配比您需要的更多,然后将分配的对象调整为所需的大小。 但是,对于按大小严格隔离块的实现,这实际上可能是有害的(额外分配和memcpy ),而在其他情况下,可能会增加分段(通过分割大块空闲块来存储新的小对象,而不是使用现有的小免费块)。

我不知道我是否会说realloc 鼓励不好的做法,但这是我要注意的一个function。

一般malloc家庭怎么样? 我所看到的绝大多数大型的,长期存在的程序都使用dynamic内存分配,就好像它是免费的。 当然,实时开发人员知道这是一个神话,不小心使用dynamic分配会导致内存使用的灾难性爆炸和/或地址空间碎片化,以至内存耗尽。

在没有机器级指针的高级语言中,dynamic分配并不是那么糟糕,因为实现可以在程序的生命周期中移动对象并对内存进行碎片整理,只要能够保持对这些对象的引用是最新的。 一个非常规的C实现也可以这样做,但是计算细节并不重要,并且在所有的指针解引用中都会产生非常大的代价,并且使得指针相当大,所以出于实际的目的,在C中是不可能的。

我的怀疑是,正确的解决scheme通常是长期存在的程序像往常一样使用malloc来执行它们的小规模分配,但是要保留大型的,长期存在的数据结构,以便可以重build和定期更换,或者作为包含构成应用程序中的单个大单元数据(如浏览器中的整个网页表示)的大量malloc块,或者具有固定大小的内存中高速caching或存储器的盘上磁盘,映射的文件。

在一个完全不同的方法中,当atan2()时,我从来没有真正理解atan()的好处。 不同之处在于atan2()接受两个参数,并在范围-π.. +π中的任何位置返回一个angular度。 此外,它避免了被零误差和精度误差的丢失(将很小的数字除以非常大的数字,反之亦然)。 相比之下, atan()函数只返回-π/ 2 .. +π/ 2范围内的值,并且必须事先进行分割(我不记得atan()可以不用的情况有一个分裂,简单地产生一个arctangents表)。 提供1.0作为atan2()的除数,当给出一个简单的值时,不是在推动极限。

另一个答案,因为这些都没有真正的相关, rand

  • 它具有未指定的随机质量
  • 它不可重入

其中一些function正在修改某些全局状态。 (在Windows中)这种状态是每个单独的线程共享 – 你可以得到意想不到的结果。 例如,在每个线程中rand的第一次调用将得到相同的结果,并且需要一些注意使其成为伪随机数,但是是确定性的(用于debugging目的)。

basename()dirname()不是线程安全的。