Подтвердить что ты не робот

Почему компоновщик С++ позволяет выполнять функции undefined?

Этот код на С++, возможно, неожиданно выводит 1.

#include <iostream>

std::string x();

int main() {

    std::cout << "x: " << x << std::endl;
    return 0;
}

x - это прототип функции, который, как представляется, рассматривается как указатель на функцию, и в разделе С++ Standard 4.12. Логические преобразования говорят:

4.12 Логические преобразования [conv.bool] 1 Значение арифметики, неперечисленное перечисление, указатель или указатель на тип члена может быть преобразуется в prvalue типа bool. Нулевое значение, значение нулевого указателя, или значение указателя нулевого элемента преобразуется в значение false; любое другое значение преобразован в true. Для прямой инициализации (8.5), prleue типа std:: nullptr_t может быть преобразован в prvalue типа bool; итоговое значение false.

Однако x никогда не привязан к функции. Как и следовало ожидать, C-компоновщик не допускает этого. Однако в С++ это не проблема. Может ли кто-нибудь объяснить это поведение?

4b9b3361

Ответ 1

Что здесь происходит, так это то, что указатель функции неявно преобразован в bool. Это определяется [conv.bool]:

Значение нуля, значение нулевого указателя или значение указателя нулевого элемента преобразуется в false; любое другое значение преобразуется в true

где "значение нулевого указателя" включает указатели на нулевые функции. Поскольку указатель функции, полученный от распада имени функции, не может быть нулевым, это дает true. Вы можете увидеть это, включив в команду вывода << std::boolalpha.

Ниже приведена ошибка связи в g++: (int)x;


Относительно того, разрешено это поведение или нет, С++ 14 [basic.odr.ref]/3 говорит:

Функция, имя которой отображается как потенциально оцениваемое выражение, используется odr, если оно уникальный результат поиска или выбранный элемент набора перегруженных функций [...]

который охватывает этот случай, так как x в выходном выражении рассматривается с объявлением x выше, и это уникальный результат. Тогда в /4 имеем:

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

поэтому программа плохо сформирована, но диагностика не требуется, что означает, что поведение программы полностью undefined.

Кстати, это предложение подразумевает, что для x(); не требуется никакой ошибки связи, однако с точки зрения качества реализации; это было бы глупо. Курс, который выбрал здесь g++, кажется мне разумным.

Ответ 2

X не нужно "привязывать" к функции, потому что вы указали в своем коде, что такая функция существует. Поэтому компилятор может с уверенностью предположить, что адрес этой функции не должен быть NULL. Чтобы это было возможно, вам нужно объявить функцию слабым символом, а вы этого не сделали. Linker не протестовал, потому что вы никогда не называете свою функцию (вы никогда не используете ее фактический адрес), поэтому он не видит проблем.

Ответ 3

[basic.def.odr]/2:

Функция, имя которой отображается поскольку потенциально оцениваемое выражение используется odr, если оно является уникальным результатом поиска или выбранным членом множество перегруженных функций (3.4, 13.3, 13.4), если только это не чистая виртуальная функция, и ее имя не указано явно Квалифицированный.

Следовательно, строго говоря, код odr - использует функцию и поэтому требует определения.
Но современные компиляторы поймут, что функции точного адреса фактически не имеют отношения к поведению программы и, таким образом, будут исключать использование и не требуют определения.

Также обратите внимание на то, что [basic.def.odr]/3 указывает:

Каждая программа должна содержать ровно одно определение каждого нестрочного функция или переменная, которая используется в этой программе odr; нет диагностических требуется.

Реализация не обязана останавливать компиляцию и выдавать сообщение об ошибке (= диагностика). Он может делать то, что он считает лучшим. Другими словами, любое действие разрешено, и мы имеем UB.