Nano Hash - криптовалюты, майнинг, программирование

Увеличение изменчивой переменной в C

Рассмотрим следующие три выражения:

++x;
x += 1;
x = x + 1;

Насколько мне известно, они идентичны по семантике, если не учитывать перегрузку операторов в C++. Однако сегодня я прочитал утверждение, что они отличаются, особенно когда x объявляется volatile.

Чтобы проверить это утверждение, я написал следующее и скомпилировал его для PowerPC, AMD64, ARMv6 и 68k:

#include <stdint.h>

static volatile uint64_t x = 0;

void a(void)
{
    ++x;
}

void b(void)
{
    x += 1;
}

void c(void)
{
    x = x + 1;
}

На всех четырех этих платформах три функции генерировали одинаковый вывод на ассемблере, будь то -O1 или -O3. На AMD64 это было всего две инструкции:

incq    _x(%rip)
retq

Следовательно, есть ли правда в этом утверждении? Если да, то в чем разница, и как я могу это выставить?

NB: я прекрасно понимаю, что volatile не гарантирует атомарности. Это не то, о чем я спрашиваю здесь - если только сама атомарность не является тем, что отличается между тремя.


  • Проект стандарта C11, 6.5.3.1 Prefix increment and decrement operators Section 2 [...]The expression ++E is equivalent to (E+=1).[...]. 6.5.16.2 Compound assignment, Section 3 [...]A compound assignment of the form E1 op = E2 is equivalent to the simple assignment expression E1 = E1 op (E2), except that the lvalue E1 is evaluated only once[...] 22.09.2015
  • @EOF OP может быть больше заинтересован в третьем случае, так как кажется, что в выражении есть доступ для чтения и записи, что должно быть отражено в машинном коде ... собираюсь проверить это 22.09.2015
  • Сноска в C11§6.5.16/3: 111) The implementation is permitted to read the object to determine the value but is not required to, even when the object has volatile-qualified type. 22.09.2015
  • Хорошо, только эта сноска поясняет, что это не ошибка компилятора ;) (и их было много вокруг volatile). Еще одна ситуация, определяемая реализацией, о которой я не знал... 22.09.2015
  • @Kninnug: Интересно. Но как я это прочитал, речь идет о выражении вроде b = a = whatever, где значение правильного присваивания используется дальше. 22.09.2015
  • Сноски @Kninnug не являются нормативными, и в проекте стандарта C++, похоже, нет подобного примечания. 22.09.2015
  • Хм. Хорошо, подождите... один шаг назад: x = x + 1; явно содержит чтение и запись. А как насчет ++x? На уровне сборки inc извлекает и сохраняет, так что и то, и другое... 22.09.2015
  • @FelixPalmen: что еще более важно, чтение volatile и хранилище volatile не переупорядочиваются друг с другом в файле incq. Очевидно, так как они зависимы. 22.09.2015
  • @EOF Это одно утверждение, другое состоит в том, что каждая выборка и сохранение на самом деле происходит ровно один раз. Так что, если на уровне C ++x считается выполнением обоих, я думаю, все в порядке. 22.09.2015
  • Ну, семантически эти формы различны. Первый — это приращение, а второй — просто сокращение для третьего, которое НЕ является приращением, а скорее тремя операциями — чтением, сложением и присваиванием. Я считаю, что компиляторы, заменяющие 3 операции одним приращением для volatile, не обязательно правильны. 22.09.2015
  • @SergeyA: Ты читал мой первый комментарий? Если да, то почему вы утверждаете, [...] что второй - это просто сокращение от третьего [...], когда стандарт явно противоречит этому? 22.09.2015
  • @SergeyA - Как вы можете увеличить значение, если вы не читаете его, чтобы узнать, что это такое? Что такое приращение, кроме чтения, сложения и присваивания? 22.09.2015
  • Кто-то должен сжать это до хорошего ответа. Указано ли стандартом, что, например. ++x означает чтение и запись? Или это просто здравый смысл? 22.09.2015
  • Цитата @EOF из стандарта соответствует моему предыдущему пониманию взаимосвязи между тремя выражениями. Отмечу еще: 5.1.2.3 Program Execution [...] Accessing a volatile object, modifying an object, modifying a file, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment. Evaluation of an expression in general includes both value computations and initiation of side effects. 22.09.2015
  • @SergeyA ЦП AMD64 внутренне преобразует inc в отдельные операции чтения, изменения и записи. Чтобы сделать ее атомарной, неделимой операцией (как ее видят другие процессоры), она должна быть lock inc. 22.09.2015
  • @JSF Так и есть - это контекст сноски 111 в соответствии с комментарием Книннуга (т. Е. Он затрагивает проблему). 22.09.2015
  • @Chromatix не имеет значения, является ли он атомарным. Я думаю, что СергейА упустил здесь только тот факт, что inc будет извлекать и сохранять значение, по крайней мере, на любой известной мне архитектуре... 22.09.2015
  • @FelixPalmen Это то, что я пытался донести. :) 22.09.2015
  • Очевидно, что это никак не влияет на переносимое поведение volatile. Таким образом, любые различия будут зависеть от платформы. 22.09.2015

Ответы:


1

В черновом стандартном разделе C++ 5.3.2 [expr.pre.incr] говорится:

Если x не имеет типа bool, выражение ++x эквивалентно x+=1.

и 5.17 [expr.ass] говорит:

Поведение выражения формы E1 op = E2 эквивалентно E1 = E1 op E2, за исключением того, что E1 оценивается только один раз.

Итак, ++x и x += 1 эквивалентны.

Теперь единственный случай, когда x += 1 отличается от x = x + 1, заключается в том, что E1 оценивается только один раз. В данном конкретном случае это не имеет значения, но мы можем придумать случай, когда это имеет значение:

#include <stdint.h>

volatile uint64_t x = 0;
volatile uint64_t y[2] = {0} ;

void c(void)
{
   y[x] = y[x] + 1;
}

в этом случае x будет оцениваться дважды, в отличие от этого случая:

void b(void)
{
   y[x] += 1;
}

и показ сеанса Godbolt для b():

b():                                  # @b()
movq    x(%rip), %rax
incq    y(,%rax,8)
retq

и для c():

c():                                  # @c()
movq    x(%rip), %rax
movq    y(,%rax,8), %rax
incq    %rax
movq    x(%rip), %rcx
movq    %rax, y(,%rcx,8)
retq

Насколько я могу судить, это относится и к C11. Из раздела C11 6.5.3.1 Префиксные операторы увеличения и уменьшения:

Выражение ++E эквивалентно (E+=1).

и из раздела 6.5.16.2 Составное присвоение:

Составное присваивание формы E1 op= E2 эквивалентно простому выражению присваивания E1 = E1 op (E2), за исключением того, что lvalue E1 оценивается только один раз.

22.09.2015
  • Интересно, что он говорит, если не типа bool. А как насчет типов с определенной перегрузкой операторов? (Да, я педант.) 22.09.2015
  • Перегруженные операторы @Chromatix будут преобразованы в вызов функции и будут зависеть от реализации, поэтому стандарт не может предоставить гарантии для таких случаев. 22.09.2015
  • @Chromatix в отношении логического случая стандарт говорит The operand of prefix ++ is modified by adding 1, or set to true if it is bool (this use is deprecated)., поэтому он строго не эквивалентен и является устаревшим использованием. 22.09.2015
  • Я добавил тестовые примеры доступа к массиву в свою программу, и они также производят отличающийся друг от друга код на других архитектурах. Интересно, что вычисления адресов для y[x] = y[x] + 1 выполняются конвейерно на платформах RISC, так что выборка x для обоих происходит до выборки y[x]. 22.09.2015
  • @Chromatix Я заметил, что gcc делает то же самое, а clang - нет, я не уверен, что об этом думать. 22.09.2015
  • Насколько я знаю, оператор не содержит никаких точек последовательности, поэтому изменение порядка доступа разрешено, даже если оно включает в себя изменчивые объекты. Это одна из причин, почему volatile не является атомарным. 22.09.2015
  • @Chromatix, да, верно, не уверен, о чем я думал. В C11/C++11 говорят, что порядок оценки не указан. 22.09.2015
  • y[x] = y[x] + 1 не имеет ничего общего с x = x + 1. 22.09.2015

  • 2

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

    Хотя x = x + 1 дважды упоминает x, левая сторона x не оценивается. То есть не полностью: его значение не вычисляется. Он оценивается только в той мере, в какой определяется место, куда пойдет присвоенное значение.

    Так что, возможно, здесь имеет место двойная оценка местоположения: левая сторона определяет местоположение x, а правая — тоже. Но определение местоположения не предполагает доступа к самому местоположению.

    Для некоторых типов выражений определение местоположения требует доступа к значениям. Например:

    a[i] = a[i] + 1;
    

    Это сильно отличается от

    i = i + 1
    

    потому что i здесь является лишь вторичной переменной, значение которой должно быть известно, чтобы определить место хранения a[i]i сама по себе даже не увеличивается). Если i равно volatile, то два абстрактных обращения к нему в a[i] = a[i] + 1 должны соответствовать двум фактическим обращениям.

    22.09.2015
    Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

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

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

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

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

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

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..