为什么我的(重新)strlen实现错误?

Bál*_*ász 4 c c++ string recursion strlen

我想出了这个小代码,但所有专业人士都表示它很危险,我不应该写这样的代码.任何人都可以在"更多"细节中突出其漏洞吗?

int strlen(char *s){ 
    return (*s) ? 1 + strlen(s + 1) : 0; 
}
Run Code Online (Sandbox Code Playgroud)

Rei*_*ica 6

它本身没有漏洞,这是完全正确的代码.当然,这是过早的悲观.除了最短的字符串之外,它将耗尽堆栈空间,并且由于递归调用,它的性能会很糟糕,但是否则就可以了.

尾调用优化很可能无法处理这样的代码.如果你想危险地生活并依赖尾调优化,你应该改为使用尾调用:

// note: size_t is an unsigned integertype

int strlen_impl(const char *s, size_t len) {
    if (*s == 0) return len;
    if (len + 1 < len) return len; // protect from overflows
    return strlen_impl(s+1, len+1);
}        

int strlen(const char *s) {
   return strlen_impl(s, 0);
}
Run Code Online (Sandbox Code Playgroud)

  • 当使用-O2编译时,clang和gcc都会将问题中的代码转换为循环.(GCC 4.8.1,Clang 3.3) (5认同)
  • @JohnBartholomew:我甚至怀疑甚至依赖于尾部调用优化,更不用说转换到循环......标准中的任何内容都不保证任何一个. (2认同)
  • 基本上,问题不在于某些编译器会做什么,问题在于编码它是否是一个好主意.是的,我确定有一些模板元编程可以产生这样的代码,编译器应该处理它,但这个答案是新手级别,让我们不要混淆. (2认同)

joh*_*ohn 5

危险它有点延伸,但它是不必要的递归,并且可能比迭代替代方案效率低.

我想还有一个非常长的字符串存在堆栈溢出的危险.


R..*_*R.. 5

此代码中存在两个严重的安全漏洞:

  1. 使用int而不是size_t返回类型.如上所述,字符串长于INT_MAX将导致此函数通过整数溢出调用未定义的行为.在实践中,这可能导致计算strlen(huge_string)为一些小的值,如1,malloc"错误的内存量,然后执行strcpy它,导致缓冲区溢出.

  2. 无限递归,可以溢出堆栈,即堆栈溢出.:-)编译器可以选择优化递归到循环(在这种情况下,它可以使用当前的编译器技术),但不能保证它会.在最好的情况下,堆栈溢出只会使程序崩溃.在最坏的情况下(例如,在没有保护页面的线程上运行),它可能会破坏不相关的内存,可能会产生任意代码执行.