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并不难。 如果程序因分配错误而失败,那么清理旧指针通常不是必须的。
我会把printf
和scanf
放在这个清单上。 事实上,你必须使格式化说明符完全正确,这使得这些函数使用起来非常棘手,而且很容易出错。 读取数据时,也很难避免缓冲区溢出。 而且,当好心的程序员指定客户端指定的string作为printf的第一个参数时,“printf格式string漏洞”可能会造成无数的安全漏洞,只能发现堆栈被捣毁,安全性受到影响。
任何操作全局状态的函数,如gmtime()
或localtime()
。 这些function不能在多个线程中安全使用。
编辑: rand()
在看起来在同一类别。 至less没有线程安全的保证,在我的Linux系统上,手册页警告说它是不可重入的并且不是线程安全的。
我的黑客之一是strtok()
,因为它是不可重入的,并且因为它将string处理成碎片,所以在它所分离的每个令牌的末尾插入NUL。 这个问题是军团的; 令人痛心的是经常被吹捧为一个问题的解决scheme,但是它本身也经常是一个问题。 并不总是 – 它可以安全地使用。 但是,只有当你小心。 大多数函数也是如此,除了gets()
这个不能安全使用的值得注意的例外。
关于realloc
已经有一个答案,但我有不同的看法。 很多时候,我看到有些人写的是free
, malloc
– 换句话说,当他们有一个缓冲区充满垃圾,需要改变大小之前,存储新的数据。 这当然会导致潜在的巨大caching抖动垃圾堆memcpy
即将被覆盖。
如果正确使用增长的数据(避免最坏情况下的O(n^2)
性能将对象扩大到n
,即在空间不足的情况下增加缓冲区的几何形状而不是线性), realloc
会带来可疑的好处简单地做你自己的新的malloc
, memcpy
和free
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()
不是线程安全的。