手动定义strlen时出现奇怪的行为

13

我不小心写了下面这个有趣的片段:

#include <iostream>
#include <cstring>

size_t strlen(const char* str) {
    std::cout << "hello";
    return 0;
}

int main() {
    return std::strlen("sdf");
}

出乎意料的是,在 GCC 5.1 中输出结果为 "hello",这意味着我的 strlen 被调用了。更有趣的是,如果我移除 return,即使用 std::strlen("sdf"); 替换 main 函数,就没有任何输出!

我还尝试了 Clang,它会调用真正计算字符串长度的函数 std::strlen(且没有任何输出)。这正是我所期望见到的。

这是如何解释的?定义自己的 strlen 函数是否被视为未定义行为?


3
请参阅 [reserved.names] 和 [depr.c.headers]。 (翻译:See [reserved.names] and [depr.c.headers].) - Kerrek SB
在我的电脑上,您的示例导致段错误(Linux,gcc版本6.2.1 20160830)。我没有预料到这个结果。我期望代码打印“hello”,然后以0的状态码退出操作系统。 - DusteD
你的示例代码我也会遇到 segfault(gcc 5.4)。我建议不要在全局命名空间中提供 strlen 函数,因为这样会覆盖 libc 版本(采用 "C" 链接方式),如果你首先包含 <cstring>,则该问题变得非常罕见和“危险”。这自然会覆盖 std::strlen,可能会在库的其他地方使用。例如,std::cout 看起来会生成对 strlen 的调用。 - davmac
你的代码中有错误。 - Raindrop7
3个回答

13

这里没有什么有趣的东西,只是一个函数重载和一些未定义的行为。您用自己的版本重载了库函数strlen()。由于在GCC实现中std::strlen仅是命名空间std内的库函数调用,所以您得到了您所看到的结果。

下面是cstring的相关摘录:

namespace std _GLIBCXX_VISIBILITY(default)
{
 _GLIBCXX_BEGIN_NAMESPACE_VERSION

  using ::strlen;
  ...

当您删除返回语句时,GCC会优化掉整个调用,因为它知道strlen是一个没有副作用的函数,并且它实际上是一个保留名称,不应该被重载。我假设编译器可能会在这里给您一个警告,但遗憾的是,它没有,因为它不是必需的。


1
你能解释一下如果去掉 return 语句的行为吗?函数调用会被优化掉吗(我猜测 gcc 可以做到这一点,因为它是一个标准库函数)? - Kevin
@Kevin,是的,但我会在答案中详细解释。 - SergeyA
这不是函数重载。std::strlen有参数(char const *);不能使用相同的参数列表重载函数。 - M.M
@M.M,这是一个公正的观点。但它的行为就好像它被过载了一样,你同意吗? - SergeyA

4
根据C ++ 14 [extern.names] / 3, :: strlen 被保留:

Standard C库中声明为外部链接的每个名称都被保留供实现使用,作为带有“ extern“C”链接”的名称,在命名空间std和全局命名空间中均如此。

如果在保留名称[reserved.names] / 2上下文中声明或定义名称,则使用它的效果是未定义行为,除非明确允许本条款。
因此,您的程序具有未定义的行为。

0

您在默认的std命名空间中定义了strlen,从而覆盖了标准的strlen。

有时调用您的strlen,有时调用标准的strlen,这可能与许多strlen实现是宏而不是函数有关。甚至可能是用汇编语言实现的。

如果它是一个宏,那么将运行标准的strlen。 此外,如果您删除return,则优化器可以删除函数调用。您可以使用-O0进行比较。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接