2016-06-27 1 views
6

Мы знаем, что логический оператор AND (&&) гарантирует оценку слева направо.Может ли оптимизатор компилятора C нарушить короткое замыкание и изменить порядок доступа к памяти для операндов в логическом выражении?

Но мне интересно, если оптимизатор компилятора может когда-либо реорганизовать инструкции доступа к памяти для *a и b->foo в следующем коде, т.е. оптимизатора пишет инструкции, которые пытаются получить доступ *b перед обращением *a.

(Рассмотрим как a и b быть указатели на области памяти в куче.)

if (*a && b->foo) { 
    /* do something */ 
} 

Можно подумать, что && вызывает точку последовательности, так что компилятор должен испускать инструкции для доступа *a перед обращением *b но после прочтения принятого ответа на https://stackoverflow.com/a/14983432/1175080, я не уверен. Если вы посмотрите на этот ответ, между операторами есть полуколоны, и они также устанавливают точки последовательности, и поэтому они также должны препятствовать переупорядочению, но ответ там, кажется, указывает, что им нужен уровень памяти уровня компилятора, несмотря на наличие точек с запятой.

Я имею в виду, если вы утверждаете, что && устанавливает точку последовательности, то это верно для точек с запятой в коде на https://stackoverflow.com/a/14983432/1175080. Тогда почему в этом коде требуется защитный барьер на уровне компилятора?

+0

и в каком сценарии это происходит _may_? –

+3

Да. См. Правило [as-if] (https://en.wikipedia.org/wiki/As-if_rule). – AlexD

+0

Я думал, что '&&' вызвало точку последовательности. Таким образом, выборка/оценка 'b' должна быть' * a' ложной. Hmm @ AlexD as-if берет на себя внимание. – chux

ответ

6

Система может оценивать b->foo до тех пор, пока она не достигнет того, что превышает ее способность выполнять спекулятивно. Большинство современных систем могут обрабатывать спекулятивную ошибку и игнорировать ошибку, если окажется, что результаты операции никогда не используются.

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

+0

Я думаю, что ответ смешивает компилятор 'as-if' с возможностями ЦП. Компилятору в этом случае должно быть разрешено изменять порядок инструкций, хотя аппаратное обеспечение/ЦП может. – Myst

+0

@Myst Нет никакой разницы. Компилятор сообщает CPU, что делать. Стандарты не различают. С точки зрения кода C, не имеет значения, что делает оптимизацию. Я сделал сознательный выбор, чтобы не дать нерелевантности сделать мой ответ более запутанным. –

+0

Чтобы исправить мои комментарии раньше, я хотел написать: «В этом случае компилятору должно быть ** не ** разрешено изменять порядок инструкций, хотя аппаратное обеспечение/ЦП может« ... по отношению к нему не делая практических различий, вы, вероятно, правы, но я думаю, что это помогает знать, где происходит оптимизация. Там больше человеческого доверия к оптимизации ЦП, а затем оптимизации компилятора (есть опасения, что компилятор «неправильно понял» наши намерения). Если этот страх нелогичен или нет, это, вероятно, не имеет значения. – Myst

3

В соответствии со стандартом ISO С11, в §C, приложение С, он отметил, что

Ниже приведены последовательности точки, описанные в ... между оценками первого и второго операндов из следующие операторы: логический И & & (6.5.13); логический ИЛИ || (6.5.14); запятая, (6.5.17).

И, как указано в §5.1.2.3:

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

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

+0

То же самое можно сказать и о точках с запятой, верно? Тогда зачем нам компилировать барьер уровня памяти в коде, представленном в этом ответе? - http://stackoverflow.com/a/14983432 –

+0

Доступ к памяти - это не то же самое, что оценка выражения, OP не указывала, что 'int * d' является volatile, поэтому компилятор безопасен для предварительной выборки. Никакой переуступка инструкций, вызывающих оценку, фактически не возникает в вопросе, который вы предоставили. – Jack

+0

@Jack Как это соотносится с правилом «как-если» и «_ Семантические описания в этом международном стандарте описывают поведение абстрактной машины , в которой проблемы оптимизации неактуальны.» «? – AlexD

4

Но мне интересно, если оптимизатор компилятора может когда-либо реорганизовать инструкции доступа к памяти * а и b-> Foo в следующем коде, т.е. оптимизатора пишет инструкции, которые пытаются получить доступ к * б перед доступом к * а.

if (*a && b->foo) { 
    /* do something */ 
} 

С семантика для выражения требуют, чтобы *a быть оценены во-первых, и что b->foo быть оценена только тогда, когда *a оценивали с нуля. @ Ответ Джека обеспечивает основу для этого в стандарте.Но ваш вопрос касается оптимизаций, которые выполняет компилятор, и в стандарте указывается, что

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

(C2013, 5.1.2.3/1)

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

В частности, в вашем примере коде, если компилятор может доказать (или готово взять на себя), что оценки *a и b->foo не имеют никакого внешне видимое поведение и независимы - ни имеют побочный эффект, который влияет на оценках или побочные эффекты другого - тогда он может испускать код, который оценивает b->foo безоговорочно, до или после оценки *a. Обратите внимание, что если b имеет значение NULL или содержит недопустимое значение указателя, то оценка b->foo имеет неопределенное поведение. В этом случае оценка b->foo не зависит от какой-либо другой оценки в программе.

Как отмечает @DavidSchwartz, однако, даже если значение b «s может быть пустым или недействительным, компилятор может все еще быть в состоянии излучать код, который умозрительно продолжается, как если бы они были действительными, и откатывается в том случае, если что оказывается, не так. Ключевым моментом здесь является то, что внешне видимое поведение не подвержено действительной оптимизации.

+0

Что делать, если можно доказать, что 'b' не является NULL, но не может быть доказано, указывает ли' b' на действительную выделенную память ? –

+0

@ LoneLearner Затем система может выполнять спекулятивное исполнение, а затем решает выкинуть ее результаты. –

+0

@LoneLearner Я переписал последний бит, чтобы уточнить. –

3

Прежде всего я считаю, что && означает встроенную версию логического оператора И.

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

Для примера, компилятор C++ разрешено вводить переназначения при следующих условиях:

  1. a примитивный указатель (то есть его тип не является классом, который перегружает operator*).
  2. b примитивный указатель (т.е. его тип не является классом, который перегружает operator->)
  3. b, как известно, разыменовываемое независимо от значения *a

Если 1. не имеет места, то пользовательский operator* может иметь побочный эффект изменения значения b->foo.

Если 2. не выполняется, то определенный пользователем operator-> может изменить значение *a, или выбросить, или производить другой наблюдаемый побочный эффект (например, печать что-то), что не должно было показано выше были оценены *a до false.

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

Компилятор C, естественно, должен выполнить только 3-й чек.

Фактически, даже если *a и b->foo связаны с перегрузкой оператора, компилятор C++ может по-прежнему изменять порядок инструкций, когда эти операторы могут быть встроены, а компилятор не обнаруживает ничего опасного.

+0

Есть ли у C перегрузка оператора? –

+0

@WeatherVane Не обращал внимания на теги. Спасибо, что поймал это. – Leon

 Смежные вопросы

  • Нет связанных вопросов^_^