Разные функции имеют разные адреса?

Рассмотрим эти две функции:

void foo() {}
void bar() {}

это гарантировано, что &foo != &bar?

Так же,

template<class T> void foo() { }

это гарантировано, что &foo<int> != &foo<double>?


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

MSVC активно COMDAT сворачивает функции, поэтому две функции с одинаковой реализацией можно превратить в одну функцию. Как побочный эффект, две функции имеют один и тот же адрес. У меня сложилось впечатление, что это незаконно, но я не могу найти, где в стандарте это запрещено.

Золотой линкер также складывает функции, как с safe а также all установка. safe означает, что если адрес функции взят, он не складывается, а all складывается, даже если адрес взят. Так золотая складка safe ведет себя как-будто функции имеют разные адреса.

Хотя свертывание может быть неожиданным, и есть код, который опирается на разные (идентичные реализации) функции, имеющие разные адреса (поэтому может быть опасно сворачивать), действительно ли это незаконно в соответствии с текущим стандартом C ++? (C ++ 14 на данный момент) (Естественно, как-будто safe складывание легально)

52

Решение

5.10 Операторы равенства [expr.eq]

1 == (равно) и тому != (не равно) группа операторов слева направо. Операнды должны иметь арифметику, перечисление, указатель или указатель на тип или тип члена std::nullptr_t, Операторы == а также != оба дают true или же falseто есть результат типа bool, В каждом случае ниже операнды должен иметь тот же тип после применения указанных преобразований.
2 Если хотя бы один из операндов является указателем, преобразования обоих указателей (4.10) и квалификационные преобразования (4.4) выполняются для обоих операндов, чтобы привести их к их составному типу указателя. (Пункт 5). Сравнение указателей определяется следующим образом: Два указателя сравниваются равными, если они оба равны нулю, оба указывают на одну и ту же функцию или оба представляют один и тот же адрес (3.9.2), в противном случае они сравниваются неравно.

Давайте возьмем последний бит за битом:

  1. Два нулевых указателя сравниваются одинаково.
    Хорошо для вашего здравомыслия.
  2. Два указателя на одну и ту же функцию сравниваются одинаково.
    Все остальное было бы крайне удивительно.
    Это также означает, что только одна версия inline-функция может когда-либо получить его адрес, если вы не хотите делать сравнения указателей на функции чрезмерно сложными и дорогостоящими.
  3. Оба представляют один и тот же адрес.
    Теперь это то, о чем все. Отбрасывая это и уменьшая if and only if к простому if оставил бы это для интерпретации, но это четкий мандат, чтобы сделать любые две функции идентичными, до тех пор, пока это не изменяет наблюдаемое поведение соответствующей программы.
2

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

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

В соответствии с пунктом 5.10 [expr.eq] только два указателя функции
сравнить равно, если они указывают на одну и ту же функцию
. Тем не менее, как
оптимизация, реализации в настоящее время являются псевдонимами функций тот
имеют идентичные определения. Не ясно, нужен ли Стандарт
явно иметь дело с этой оптимизацией или нет.

и ответ был:

Стандарт четко определяет требования и реализации являются
свободно оптимизировать в рамках правила «как будто»
.

Вопрос задает два вопроса:

  • Можно ли считать эти указатели равными?
  • Это нормально, чтобы объединить функции

На основании комментариев я вижу две интерпретации ответа:

  1. Эта оптимизация в порядке, стандарт дает реализации эту свободу под как будто правило. как будто правило покрыто в разделе 1.9 и означает, что реализация должна только эмулировать наблюдаемое поведение по отношению к требованиям стандарта. Это все еще моя интерпретация ответа.

  2. Этот вопрос полностью игнорируется, и в заявлении просто говорится, что не требуется никаких изменений в стандарте, поскольку как будто правила охватывает это, но толкование оставлено в качестве упражнения для читателя. Хотя я признаю, что из-за краткости ответа я не могу отклонить эту точку зрения, в итоге она оказывается совершенно бесполезной. Это также кажется несовместимым с ответами в другом NAD проблемы, которые, насколько я могу судить, указывают на проблему, если они существуют.

Что говорится в проекте стандарта

Поскольку мы знаем, что имеем дело с как будто правило, мы можем начать там и отметить, что раздел 1.8 говорит:

Если объект не является битовым полем или подобъектом нулевого базового класса
размер, адрес этого объекта является адресом первого байта его
занимает. Два объекта, которые не являются битовыми полями, могут иметь одинаковые
адрес, если один является подобъектом другого, или если хотя бы один является
подобъект базового класса нулевого размера, и они бывают разных типов;
в противном случае они должны иметь разные адреса.4

и обратите внимание 4 говорит:

В соответствии с правилом «как будто» реализации разрешено хранить два
объекты с тем же адресом машины или не хранить объект вообще, если
программа не может увидеть разницу

но записка из этого раздела гласит:

Функция не является объектом, независимо от того, занимает ли он
хранение так, как это делают объекты

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

Далее у нас есть раздел 5.10 Операторы равенства который говорит (акцент мой):

[…] Два указателя сравниваются равными, если они оба равны нулю, оба указывают на
одна и та же функция или оба представляют один и тот же адрес
(3.9.2),
в противном случае они сравниваются неравно.

что говорит нам, что два указателя равны, если они:

  • Нулевые указатели
  • Указать на ту же функцию
  • Представлять один и тот же адрес

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

наблюдения

Кит Томпсон сделал несколько замечательных замечаний, которые, как мне кажется, стоит добавить к ответу, поскольку они затрагивают основные проблемы, которые он затрагивает, он говорит:

Если программа печатает результат &фу == &бар, это наблюдаемое поведение; рассматриваемая оптимизация изменяет наблюдаемое поведение.

с которым я согласен, и если бы мы могли показать, что существует требование, чтобы указатели были неравными, что действительно нарушало бы как будто правило но пока мы не можем этого показать.

а также:

[…] рассмотрим программу, которая определяет пустую функцию и использует их
адреса как уникальные значения (подумайте о SIG_DFL, SIG_ERR, а также SIG_IGN
в <signal.h> / <csignal>). Назначение им одного и того же адреса
сломать такую ​​программу

Как я отметил в своем комментарии, стандарт C требует, чтобы эти макросы генерировали отличные значения, от 7.14 в С11:

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

Таким образом, хотя этот случай рассматривается, возможно, существуют другие случаи, которые делают эту оптимизацию опасной.

Обновить

Ян Губичка а gcc разработчик написал сообщение в блоге Улучшение времени соединения и межпроцедурной оптимизации в GCC 5, свертывание кода было одной из многих тем, которые он освещал.

Я попросил его прокомментировать, соответствовало ли поведение одинаковых функций одному и тому же адресу, и он сказал, что это не соответствует поведению, и такая оптимизация действительно нарушит gcc сам:

Недопустимо превращать две функции в один и тот же адрес, поэтому MSVC здесь довольно агрессивен. Это, например, нарушает сам GCC, потому что, к моему удивлению, сравнение адресов выполняется в коде скомпилированных заголовков. Это работает для многих других проектов, включая Firefox.

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

28

Да. Из стандарта (§5.10 / 1): «Два указателя одного и того же
Тип сравнения равен, если и только если они оба равны нулю, обе точки
к одной и той же функции, или оба представляют один и тот же адрес

Как только они были созданы, foo<int> а также foo<double> две разные функции, поэтому вышеизложенное относится и к ним.

10

Таким образом, проблемная часть явно фраза или оба представляют один и тот же адрес (3.9.2).

ИМО, эта часть явно предназначена для определения семантики для типов указателей на объекты. И только для типов указателей на объекты.

Фраза ссылки раздел 3.9.2, что означает, что мы должны посмотреть там. 3.9.2 рассказывает (среди прочего) об адресах, которые представляют указатели объектов. Он не говорит об адресах, которые представляют указатели функций. Что, IMO, оставляет только две возможные интерпретации:

1) Фраза просто не относится к указателям на функции. Что оставляет только два нулевых указателя и два указателя на одну и ту же функцию, сравнивая равные, что, вероятно, ожидали большинство из нас.

2) Фраза применяется. Поскольку он ссылается на 3.9.2, который ничего не говорит об адресах, которые представляют указатели функций, мы можем сделать любой два функциональных указателя сравниваются одинаково. Что очень неожиданно и, конечно, делает сравнение указателей функций совершенно бесполезным.

Таким образом, хотя технически можно утверждать, что (2) является действительный интерпретация, ИМО это не значимым интерпретация и, следовательно, следует игнорировать. И так как не все, кажется, согласны с этим, я также думаю, что требуется разъяснение в стандарте.

6
А ты уже прошел курс программирования? Супер скидка!
Прокачать скилл $$$
×