Компиляция. Почему компоновщик C ++ допускает неопределенные функции?

Этот код C ++, возможно, на удивление, печатает 1,

#include <iostream>

std::string x();

int main() {

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

x является прототипом функции, который, кажется, рассматривается как указатель на функцию, и в разделе 4.12 «Логические преобразования» стандарта C ++ говорится:

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

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

29

Решение

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

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

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

Следующее вызывает ошибку ссылки в g ++: (int)x;


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

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

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

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

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

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

28

Другие решения

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

13

[Basic.def.odr] / 2:

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

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

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

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

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

9
По вопросам рекламы [email protected]