Странное поведение с заданной вручную strlen

Случайно, я написал следующий интересный фрагмент:

#include <iostream>
#include <cstring>

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

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

Неожиданно для меня вывод "hello" в GCC 5.1, что означает, что вызывается мой strlen. Еще более интересно, если я удалю return, т.е. Заменим main только вызовом std::strlen("sdf");, ничего не будет напечатано!

Я также пробовал Clang, для которого std::strlen вызывает реальную функцию, которая вычисляет длину строки (и ничего не печатается). Это то, что я ожидал увидеть.

Как это можно объяснить? Является ли определение моей собственной функции strlen рассмотренной поведением undefined?

Ответ 1

Здесь нет ничего интересного, просто перегрузка функции и немного поведения undefined. Вы перегрузили библиотечную функцию strlen() своей собственной версией. Поскольку в реализации GCC std::strlen есть не что иное, как вызов функции библиотеки внутри пространства имен std, вы получаете результат, который видите.

Вот соответствующий выдержку из cstring:

namespace std _GLIBCXX_VISIBILITY(default)
{
 _GLIBCXX_BEGIN_NAMESPACE_VERSION

  using ::strlen;
  ...

И когда вы удаляете оператор return, GCC полностью отключает вызов, так как он знает, что strlen является функцией без побочных эффектов, и на самом деле это зарезервированное имя, которое нельзя перегружать. Я предполагаю, что компилятор может дать вам предупреждение здесь, но, увы, этого не произошло, поскольку это не требуется.

Ответ 2

В соответствии с С++ 14 [extern.names]/3, ::strlen зарезервировано:

Каждое имя из библиотеки Standard C, объявленное с внешней привязкой, зарезервировано для реализации для использования в качестве имени с внешней связью "C", как в пространстве имен std, так и в глобальном пространстве имен.

и эффект использования зарезервированного имени, [reserved.names]/2:

Если программа объявляет или определяет имя в контексте, где оно зарезервировано, кроме как явно разрешено этим разделом, его поведение undefined.

Таким образом, ваша программа имеет поведение undefined.

Ответ 3

Вы определили strlen в пространстве имен std по умолчанию, тем самым заменив стандартный.

Причина, почему иногда вызывается strlen и иногда вызывается стандартный strlen, вероятно, связана с тем, что многие реализации strlen являются макросами вместо функций. Это может быть даже реализовано в ассемблере.

Если это макрос, будет запущен стандартный. Кроме того, если вы удаляете возврат, вызов функции может быть удален оптимизатором. Вы можете сравнить с -O0.