C的“坏”function与他们的“好”替代品

C中的“坏”function是什么,他们的“好”select是什么?

为什么坏的坏,什么使好的好?

我知道,例如, gets()是“坏”的,因为它没有任何forms的边界检查。 什么是更好的select? fgets()

我听说scanf()是坏的,但我不记得为什么。 有人知道吗? 什么是最好的select?

还有更多吗?

在过去,大部分string函数都没有边界检查。 当然,他们不能只是删除旧的函数,或者修改它们的签名来包含一个上限,这会破坏兼容性。 现在,对于几乎所有这些function,都有一个可选的“n”版本。 例如:

 strcpy -> strncpy strlen -> strnlen strcmp -> strncmp strcat -> strncat strdup -> strndup sprintf -> snprintf wcscpy -> wcsncpy wcslen -> wcsnlen 

和更多。

编辑2013-12-03:

另请参阅https://github.com/leafsr/gcc-poison这是一个创build头文件的项目,如果您使用不安全的函数,则会导致gcc报告错误。;

是的,fgets(,STDIN)是gets()的一个很好的select,因为它需要一个size参数。

scanf()在某些情况下被认为是有问题的,而不是直接的“坏”,因为如果input不符合预期的格式,就不可能合理地恢复(它不会让你倒带input并尝试再次)。 如果你可以放弃格式不正确的input,这是可用的。 这里的“更好的”select是使用像fgets()或fgetc()这样的input函数来读取input块,然后使用sscanf()对其进行扫描,或者使用strchr()和strtol()等string处理函数对其进行parsing。 另请参阅下面的scanf()中的“%s”转换说明符的具体问题。

这不是一个标准的C函数,但是BSD和POSIX函数mktemp()通常是不可能安全使用的,因为在testing文件的存在和创build它之间总会有竞争条件。 mkstemp()或tmpfile()是很好的替代品。

strncpy()是一个有点棘手的函数,因为如果没有空间的话,它不会终止目的地。 你可以通过把nul-terminator自己添加到目的地,或者把目的地设置成一个空string,然后用strncat()来解决这个问题。

在某些情况下,atoi()可能是一个不好的select,因为你不能分辨出转换出现错误的时间(例如,如果数字超出了int的范围)。 使用strtol()如果这对你很重要。

strcpy(),strcat()和sprintf()会遇到与gets()类似的问题 – 它们不允许指定目标缓冲区的大小。 至less在理论上,仍然有可能安全地使用它们,但是使用strncat()和snprintf()代替你可以使用strncpy(),但是可以参考上文)。 在同一主题下,如果使用scanf()系列函数,请不要使用普通的“%s” – 指定目标的大小,例如。 “%200S”。

strtok()通常被认为是邪恶的,因为它在调用之间存储状态信息。 不要试图在multithreading环境下运行!

严格来说,有一个非常危险的function。 它是gets(),因为它的input不在程序员的控制之下。 这里提到的所有其他function都是安全的。 “好”和“坏”归结为防御性编程,即先决条件,后置条件和样板代码。

以strcpy()为例。 它有一些先决条件,程序员必须调用函数之前完成。 两个string都必须是有效的,非NULL指针指向零终止的string,并且目的地必须提供足够的空间,并在size_t范围内具有最终的string长度。 另外,两个string不允许重叠。

这是相当多的先决条件,没有一个是由strcpy()检查。 程序员必须确定他们已经完成,否则他必须在调用strcpy()之前用附加的样板代码明确地testing它们:

 n = DST_BUFFER_SIZE; if ((dst != NULL) && (src != NULL) && (strlen(dst)+strlen(src)+1 <= n)) { strcpy(dst, src); } 

已经默默地假定非重叠和零终止的string。

strncpy()确实包含了一些这样的检查,但是它增加了另一个后置条件,程序员调用函数之后必须注意,因为结果可能不是零终止的。

 strncpy(dst, src, n); if (n > 0) { dst[n-1] = '\0'; } 

为什么这些function被认为是“坏”? 因为当程序员假定错误的有效性时,他们需要为每个调用添加额外的样板代码以确保安全,程序员往往会忘记这个代码。

甚至反驳它。 以printf()系列为例。 这些函数返回一个表示错误和成功的状态。 谁检查输出到stdout或stderr是否成功? 有一种说法,即当标准频道不工作时你什么也做不了。 那么,救援用户数据和终止与错误指示退出代码的程序呢? 而不是可能的替代scheme,以后用损坏的用户数据进行崩溃和刻录。

在一个时间和金钱有限的环境中,你总是需要多less安全网,以及由此产生的最坏情况? 如果它是str函数的缓冲区溢出,那么禁止它们是有意义的,并且可能已经在内部提供了安全networking的包装函数。

关于这个的最后一个问题是:什么使你确信你的“好”替代品真的很好

任何不取最大长度参数的函数,而是依赖于结束标记(例如许多“string”处理函数)。

任何维护呼叫之间状态的方法。

  • sprintf是坏的,不检查大小,使用snprintf
  • gmtimelocaltimeuse gmtime_rlocaltime_r

为了补充一些关于这里的大多数人忘了提及。 strncpy会导致性能问题,因为它将缓冲区清除到给定的长度。

 char buff[1000]; strncpy(buff, "1", sizeof buff); 

将复制1个字符并用0覆盖999个字节

我更喜欢strclpy的另一个原因(我知道strlcpy是一个BSDism,但它很容易实现,没有理由不使用它)。

scanf()不好,因为它不防止缓冲区溢出。 我最近才知道这一点。

查看第7页(PDF第9页) SAFECode Dev实践

编辑:从页面 –

strcpy家庭
strncpy家庭
strcat家庭
scanf家庭
sprintf家族
得到家人

strcpy – 再次!

大多数人都认为strcpy是危险的,但strncpy只是很less有用的替代品。 在任何情况下,当你需要截断一个string时,通常很重要,因此通常需要检查源stringanwyay的长度。 如果是这种情况,通常memcpy是更好的替代品,因为您确切知道要复制多less个字符。

例如截断是错误的:

 n = strlen( src ); if( n >= buflen ) return ERROR; memcpy( dst, src, n + 1 ); 

允许截断,但是必须返回字符数,以便调用者知道:

 n = strlen( src ); if( n >= buflen ) n = buflen - 1; memcpy( dst, src, n ); dst[n] = '\0'; return n; 

我会说scanf是有时候,更确切地说,当你真的需要阅读一些快速的东西。 它比“cin更快”。

我记得国际信息学奥林匹克(IOI)的一个任务,那就是你需要使用scanf ,因为cin花了太多的时间。

strcpy() – 您应该使用strncpy来显式定义要复制的字节数,并避免缓冲区溢出。

Bah …华夫饼。 这些function是不安全的,因为程序员是骨头。 这有什么不好?

 char msg[100] = {'\0'}; int num = 10; //obtain num however sprintf(msg, "There are %d items for sale", num); 

只要string可以采用有符号整数的最小/最大值的长度,我不明白这是不好还是不安全。 程序员是不安全的,而不是function….