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

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

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

гарантируется, что &foo != &bar?

Аналогично,

template<class T> void foo() { }

гарантируется, что &foo<int> != &foo<double>?


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

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

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

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

Ответ 1

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

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

и ответ был:

Стандарт четко соответствует требованиям, а - реализациям свободно оптимизировать в рамках ограничений правила "как-если" .

В вопросе возникает вопрос о двух проблемах:

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

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

  • Эта оптимизация в порядке, стандарт дает реализацию этой свободы в соответствии с правилом as-if. Правило as-if рассматривается в разделе 1.9 и означает, что реализация должна только эмулировать наблюдаемое поведение в соответствии с требованиями стандарта. Это еще моя интерпретация ответа.

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

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

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

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

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

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

но в примечании из этого раздела говорится:

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

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

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

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

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

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

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

Наблюдения

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

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

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

и

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

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

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

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

Обновить

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

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

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

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

Ответ 2

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

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

Ответ 3

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

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

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

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

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

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

Ответ 4

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

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

Возьмем последний бит для бит:

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