Почему изменчивая локальная переменная оптимизируется иначе, чем изменчивый аргумент, и почему оптимизатор генерирует цикл без операции из последнего?

Задний план

Это было вдохновлено этим вопросом / ответом и последующим обсуждением в комментариях: Является ли определение «изменчивого» этим изменчивым, или GCC имеет некоторые стандартные проблемы соответствия?. Основываясь на чужой и моей интерпретации того, что должно происходить, как обсуждалось в комментариях, я отправил это в GCC Bugzilla: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=71793 Другие соответствующие ответы по-прежнему приветствуются.

Кроме того, этот поток с тех пор вызвал этот вопрос: Предоставляет ли доступ к объявленному энергонезависимому объекту через изменчивую ссылку / указатель изменчивые правила при указанном доступе ?

вступление

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

Я предполагаю, что это связано либо с очень тонкой интерпретацией Стандарта, либо (что более вероятно?) Просто с крайними случаями для используемого оптимизатора. В любом случае, хотя это скорее академический, чем практический, я надеюсь, что это будет полезно для анализа, особенно с учетом того, насколько часто это неправильно понимается volatile. Еще несколько точек данных - или, что более вероятно, указывают против - должны быть хорошими.

Вход

Учитывая этот код:

#include <cstddef>

void f(void *const p, std::size_t n)
{
    unsigned char *y = static_cast<unsigned char *>(p);
    volatile unsigned char const x = 42;
    // N.B. Yeah, const is weird, but it doesn't change anything

    while (n--) {
        *y++ = x;
    }
}

void g(void *const p, std::size_t n, volatile unsigned char const x)
{
    unsigned char *y = static_cast<unsigned char *>(p);

    while (n--) {
        *y++ = x;
    }
}

void h(void *const p, std::size_t n, volatile unsigned char const &x)
{
    unsigned char *y = static_cast<unsigned char *>(p);

    while (n--) {
        *y++ = x;
    }
}

int main(int, char **)
{
    int y[1000];
    f(&y, sizeof y);
    volatile unsigned char const x{99};
    g(&y, sizeof y, x);
    h(&y, sizeof y, x);
}

Вывод

g++ из gcc (Debian 4.9.2-10) 4.9.2 (Debian stable a.k.a. Jessie) с помощью командной строки g++ -std=c++14 -O3 -S test.cpp создает приведенный ниже ASM для main(). Версия Debian 5.4.0-6 (текущая unstable) производит эквивалентный код, но я просто случайно запустил сначала старую, так что вот она:

main:
.LFB3:
    .cfi_startproc

# f()
    movb    $42, -1(%rsp)
    movl    $4000, %eax
    .p2align 4,,10
    .p2align 3
.L21:
    subq    $1, %rax
    movzbl  -1(%rsp), %edx
    jne .L21

# x = 99
    movb    $99, -2(%rsp)
    movzbl  -2(%rsp), %eax

# g()
    movl    $4000, %eax
    .p2align 4,,10
    .p2align 3
.L22:
    subq    $1, %rax
    jne .L22

# h()
    movl    $4000, %eax
    .p2align 4,,10
    .p2align 3
.L23:
    subq    $1, %rax
    movzbl  -2(%rsp), %edx
    jne .L23

# return 0;
    xorl    %eax, %eax
    ret
    .cfi_endproc

Анализ

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

  • f() обеспечивает чтение из x на каждой итерации, предположительно из-за volatile, но просто сбрасывает результат в edx, предположительно потому, что пункт назначения y не объявлен volatile и никогда не читается, что означает изменения в нем. может быть отклонено правилом как если бы. ОК, имеет смысл.

    • Well, I mean... kinda. Like, not really, because volatile is really for hardware registers, and clearly a local value can't be one of those - and can't otherwise be modified in a volatile way unless its address is passed out... which it's not. Look, there's just not a lot of sense to be had out of volatile local values. But C++ lets us declare them and tries to do something with them. And so, confused as always, we stumble onwards.
  • g(): Что. Перемещая источник volatile в параметр передачи по значению, который по-прежнему является еще одной локальной переменной, GCC каким-то образом решает, что это не так или меньше volatile, поэтому ему не нужно читать его на каждой итерации ... но он по-прежнему выполняет цикл, несмотря на то, что его тело теперь ничего не делает.

  • h(): при использовании переданного volatile в качестве передачи по ссылке восстанавливается то же эффективное поведение, что и для f(), поэтому цикл выполняет volatile чтения.

    • This case alone actually makes practical sense to me, for reasons outlined above against f(). To elaborate: Imagine x refers to a hardware register, of which every read has side-effects. You wouldn't want to skip any of those.

Добавление #define volatile /**/ приводит к тому, что main() не работает, как и следовало ожидать. Итак, когда он присутствует, даже в локальной переменной volatile что-то делает ... Я просто не знаю, что в случае g(). Что, черт возьми, там происходит?

Вопросы

  • Why does a local value declared in-body produce different results from a by-value parameter, with the former letting reads be optimised away? Both are declared volatile. Neither have an address passed out - and don't have a static address, ruling out any inline-ASM POKEry - so they can never be modified outwith the function. The compiler can see that each is constant, need never be re-read, and volatile just ain't true -
    • so (A) is either allowed to be elided under such constraints? (acting as-if they weren't declared volatile) -
    • и (B) почему упускается только один? Некоторые volatile локальные переменные больше volatile, чем другие?
  • Отбросим на мгновение эту несогласованность: почему после того, как чтение было оптимизировано, почему компилятор все еще генерирует цикл? Ничего не делает! Почему оптимизатор не игнорирует это как если бы цикл не был закодирован?

Это странный случай из-за порядка оптимизации анализа или чего-то подобного? Поскольку код - глупый мысленный эксперимент, я бы не стал наказывать GCC за это, но было бы хорошо знать наверняка. (Или это g() ручной цикл синхронизации, о котором люди мечтали все эти годы?) Если мы придем к выводу, что ни на что из этого не влияет Стандарт, я перенесу его в их Bugzilla только для их информации.

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


person underscore_d    schedule 06.07.2016    source источник
comment
TL; DR - Если это не меняет наблюдаемого поведения программы, имеет ли это значение?   -  person Captain Obvlious    schedule 07.07.2016
comment
Стандарт C ++ 11 (я также предполагаю, что C ++ 14) говорит, что доступ к изменчивым объектам оценивается строго в соответствии с правилами абстрактной машины. Другими словами, правило «как если бы» не применяется - вы должны строго следовать правилам абстрактной машины. Я думаю, что поведение g() нарушает это; Я предполагаю, что это ошибка оптимизатора. Я также предполагаю, что большинство людей скажут, что это ошибка с низким приоритетом, и что некоторые (многие?) Не согласятся, что это ошибка в первую очередь.   -  person Michael Burr    schedule 07.07.2016
comment
@MichaelBurr Хорошая цитата не только из-за ее актуальности, но и из-за фантастического термина «абстрактная машина». Я имею дело со многими из них. Я склонен думать, что это ошибка в смысле недосмотра, поскольку можно было бы ожидать, что 2 типа локальных переменных будут вести себя одинаково - как, по моему мнению, единственная заметная разница - это время, в которое они ' реконструирован. Однако из-за сложности оптимизации и бесчисленных перестановок упорядочивания различных этапов и т. Д., Это явно не так просто. Я бы никогда не осмелился классифицировать это как ошибку с приоритетом >ittybitty, но заключение было бы отличным.   -  person underscore_d    schedule 07.07.2016
comment
@MichaelBurr Кроме того, несмотря на то, что это применимо к volatile-объявленным объектам, «как если бы» для других все еще AOK, верно? Кажется справедливым, что поскольку y не используется, бункеры компилятора записывают в него. Почему он по-прежнему считывается с x, непонятно: с x ссылкой это имеет смысл, если это аппаратный регистр, опрос которого имеет побочные эффекты. Но это не относится к случаям по стоимости ... которые, тем не менее, различаются. Кроме того: могут ли / должны ли объекты быть «сделаны» изменчивыми через volatile * кажется спорным: верхний ответ на связанном Q говорит «нет», но N1381, похоже, подразумевает «да»: open-std.org/jtc1/sc22/wg14/www/docs/n1381.pdf   -  person underscore_d    schedule 07.07.2016
comment
@CaptainObvlious модификации изменчивых переменных (даже автоматические) считаются наблюдаемым поведением   -  person M.M    schedule 07.07.2016
comment
Я бы сказал, что g - ошибка компилятора согласно Стандарту.   -  person M.M    schedule 07.07.2016
comment
@ M.M Есть ли официальное заявление о том, считается ли чтение также имеющим наблюдаемые эффекты? Я предполагаю, что - в зависимости от устройства и того, как оно настроено в электронном виде и как оно взаимодействует с языком - такие чтения могут иметь побочные эффекты для любого регистра или устройства, которое они опрашивают. Вы бы не захотели пропустить их, исключив любые чтения.   -  person underscore_d    schedule 07.07.2016
comment
@underscore_d да, чтение также является наблюдаемым поведением   -  person M.M    schedule 07.07.2016
comment
@ M.M Спасибо за вклад! Я подожду еще немного и, предполагая, что ваш анализ преобладает (черт возьми, мне он кажется совершенно правильным), отправьте его в GCC Bugzilla, если только один из них не увидит его здесь первым.   -  person underscore_d    schedule 07.07.2016
comment
@ M.M Нет, g не является ошибкой компилятора. Скажем, ассемблерный код выдал отдельные чтения, но аппаратное обеспечение ЦП объединило их, вы все равно скажете, что это ошибка компилятора? И как оптимизация более или менее заметна, если она выполняется ЦП, чем если она выполняется компилятором? Стандарт C не говорит, как должен выглядеть ассемблерный код (как это могло быть?), Он говорит, что ассемблерный код должен заставить систему делать. Любая оптимизация процессора также может быть выполнена ассемблером. Объединение операций чтения с нераспространяемыми кэшируемыми значениями - это допустимая оптимизация ЦП, поэтому оптимизация компилятора разрешена.   -  person David Schwartz    schedule 07.07.2016
comment
@DavidSchwartz, но, конечно, на систему может повлиять присутствие или отсутствие операций чтения, что делает их исключение важным для того, что делает система? и любой компилятор / ЦП, вовлеченный в такой эзотерический код, как это, несомненно, будет очень тщательно отобран для работы очень точным образом (с учетом того, как volatile определяется реализацией) - поэтому он не будет просто выдавать ложные чтения или случайным образом комбинировать их . Мне это кажется вполне подходящим для volatile, по крайней мере, для такой системы. Кроме того, ни один из этих адресов не объясняет, почему f() и g() различаются или какой из двух правильный.   -  person underscore_d    schedule 07.07.2016
comment
@DavidSchwartz Стандарт говорит, что система должна выполнять чтение области памяти, соответствующей x, один раз для каждой итерации цикла. Было бы несоответствием, если бы система (будь то компилятор, или ЦП, или что-то еще) объединила все это для одного чтения.   -  person M.M    schedule 07.07.2016
comment
@DavidSchwartz, с каких это пор volatile референты являются нераспространенными, кешируемыми значениями? Разве в действительности не прямо противоположное? volatile изначально подразумевает совместное использование (обязательно: хотя и не поточно-ориентированное) в строгом смысле: программа должна перечитывать физическую ячейку памяти по запросу, если что-то еще изменило ее. Что может быть более доступным и менее кешируемым, чем это?   -  person underscore_d    schedule 07.07.2016
comment
На случай, если кто-то рискует быть введенным в заблуждение, вот цитаты из обоих Стандартов, требующие, чтобы последовательность чтения и записи в любую заданную переменную volatile была сохранена, т.е. каждая операция должна оставаться и должна происходить в ее написанном последовательность. stackoverflow.com/questions/2535148/   -  person underscore_d    schedule 07.07.2016
comment
@ M.M Тогда большинство современных компиляторов и систем не соответствуют требованиям, потому что они не мешают процессору объединять чтения volatiles, объявленных в стеке и не используемых совместно.   -  person David Schwartz    schedule 07.07.2016
comment
@underscore_d Тогда ни один современный компилятор на процессорах x86 не соответствует требованиям, потому что они не мешают процессору объединять чтения. (Но это абсурдно неправильное прочтение стандарта.) (В этом конкретном случае компилятор знает, что эта конкретная переменная не кэшируется и не может использоваться совместно. Независимо от того, что она была объявлена ​​volatile. ЦП может объединять чтения в этом случае , и компилятор не может остановить это. Если бы вы правильно читали стандарт, большинство современных платформ нарушали бы стандарт.)   -  person David Schwartz    schedule 07.07.2016
comment
@DavidSchwartz, если что, вы утверждаете, что это тупое требование стандарта   -  person M.M    schedule 07.07.2016
comment
@ M.M Нет, это совсем не тупо. Просто это очень часто неправильно понимают. Задайте отдельный вопрос (о том, почему не каждый современный компилятор на x86 нарушает стандарт), и я объясню его более подробно.   -  person David Schwartz    schedule 07.07.2016
comment
теперь в GCC Bugzilla: gcc.gnu.org/bugzilla/show_bug.cgi?id = 71793   -  person underscore_d    schedule 07.07.2016
comment
@ M.M Я почти уверен, что стандарт не налагает требований на поведение процессора, по крайней мере, в этом случае. Если мой код C / C ++ имеет 5 непостоянных чтений в, например, аппаратный регистр, компилятор должен создать машинный код, который выполняет ровно 5 чтений по этому адресу. ЦП может объединять эти чтения или выполнять сложные аппаратные побочные эффекты (хотя единственный известный мне пример, который на самом деле происходит, - это очищаемый регистр).   -  person mbrig    schedule 07.07.2016
comment
@mbrig В этом нет смысла. Если компилятор создавал инструкции, которые заставляли ЦП объединять чтения, то компилятор не создавал инструкций, которые выполняли именно эти пять операций чтения. Стандарт не говорит о том, каким должен быть поток инструкций, он говорит о том, что должна делать система. Какие конкретные коды издает компилятор, чтобы заставить ЦП делать то, что он должен делать, - это детали реализации.   -  person David Schwartz    schedule 07.07.2016
comment
@mbrig Например, предположим, что был ЦП, который всегда менял порядок выполнения двух последовательных операций записи по одному и тому же адресу. Если код сделал i=5; i=3;, если компилятор собирался выдать обе записи, он должен был бы сначала выдать инструкцию для i=3;, в противном случае окончательное значение было бы неправильным. Конечно, вы не стали бы спорить с тем, что если бы i был volatile, на такой платформе можно было бы сделать окончательное значение 5, потому что ЦП отменил инструкции, а не компилятор. Не может быть важен порядок инструкций.   -  person David Schwartz    schedule 07.07.2016
comment
@DavidSchwartz Компилятор должен создать код, который заставляет машину вести себя так, как если бы она выполняла правильный эквивалент кода c. Если объединение чтений не изменяет поведение кода c, компилятору не нужно препятствовать тому, чтобы ЦП делал это (не то чтобы ЦП когда-либо делал такое, если бы у него были эффекты). И ЦП, и компилятор должны делать правильные вещи, изменчивые чтения не могут быть объединены компилятором, потому что ЦП может (и действительно на многих платформах) создавать побочные эффекты.   -  person mbrig    schedule 08.07.2016
comment
@mbrig Стандарт не требует изменения поведения кода C. Доступ к volatiles объявляется наблюдаемым поведением, и изменение наблюдаемого поведения не разрешено ни правилом «как если бы», ни каким-либо другим правилом. Я не понимаю твоего последнего предложения. Вы говорите, что компилятор не может объединить чтения volatiles даже в тех случаях, когда ЦП может объединить их? Если да, то на каком основании вы пришли к такому выводу? Почему имеет значение, где происходит слияние?   -  person David Schwartz    schedule 08.07.2016
comment
@mbrig Стандарт вообще ничего не говорит о том, какие инструкции должен выдавать компилятор. То, что есть машинный код, - это деталь реализации. Стандарт говорит, что машина должна делать. Важно только то, что эти инструкции заставляют делать ЦП. Если объединение чтения допустимо, это может быть выполнено компилятором, процессором или чем-то еще. Если объединение чтения является незаконным, оно не может быть выполнено системой, независимо от того, как это происходит. То, что поток инструкций - это место, где должны наблюдаться изменчивые обращения, создается и нигде в стандарте не встречается.   -  person David Schwartz    schedule 08.07.2016
comment
@DavidSchwartz: если бы компилятор либо знал, что ЦП не может быть настроен таким образом, чтобы множественные чтения не были эквивалентны одному чтению, либо он прямо задокументировал, что он не поддерживает режимы ЦП, которые отличают множественные чтения от однократного чтения, то компилятор мог бы объединить эти чтения при условии, что каждое такое чтение продолжало бы рассматриваться как побочный эффект для целей предотвращения UB, связанного с бесконечным циклом. Однако простого незнания каких-либо средств, с помощью которых можно было бы различить одно или несколько операций чтения, было бы недостаточно, чтобы ...   -  person supercat    schedule 08.07.2016
comment
... оправдывают их объединение на уровне компилятора, если компилятор явно не задокументировал, что такие функции, если они существуют, не будут поддерживаться.   -  person supercat    schedule 08.07.2016
comment
@supercat Вы говорите, что он должен делать это, потому что он имеет какое-то практическое применение? Или вы говорите, что, хотя это серьезно повлияет на производительность, тем не менее это то, что требует стандарт?   -  person David Schwartz    schedule 08.07.2016
comment
@DavidSchwartz: если автор компилятора хочет задокументировать, что определенные функции ЦП не поддерживаются, это вообще не должно влиять на производительность. В противном случае разработчик компилятора должен предположить, что программист, который хочет volatile, знает что-то, чего не знает компилятор. Есть много мест, где было бы полезно иметь, возможно, квалификаторы для параметров, чтобы явно позволить компилятору опускать квалификаторы при встраивании функции в тех случаях, когда аргументы не имеют их или имеют возвращаемые типы, соответствующие параметрам (в то время как прототип strchr не мог быть изменен ...   -  person supercat    schedule 08.07.2016
comment
... чтобы использовать такую ​​возможность, ее следует логически объявить таким образом, чтобы константность возвращаемого указателя соответствовала константе переданного). Однако в отсутствие такой вещи я бы предположил, что программисты больше заинтересованы в правильности, чем в скорости, и компилятор должен доверять тому, что программист, который использует volatile, делает это по какой-то причине, а программист, заинтересованный в скорости, написание двух версий функции - одна принимает указатель с указателем volatile, а другая принимает указатель без указания ...   -  person supercat    schedule 08.07.2016
comment
... было бы лучше, чем попытка компилятора угадать, действительно ли программисту не нужно что-то для определения изменчивости в конкретном случае. Даже на неэкзотической платформе программист может включить дескриптор сигнала во время небольшого отрезка кода и потребовать, чтобы все обращения к конкретной переменной обрабатывались как volatile, пока обработчик активен. При условии, что последний доступ к переменной перед включением обработчика - это энергозависимая запись, первый доступ после - это либо запись, либо энергозависимое чтение, и пока он включен, неквалифицированный доступ отсутствует ...   -  person supercat    schedule 08.07.2016
comment
... Я бы предположил, что программист должен иметь возможность достичь определенного поведения без необходимости использовать бесполезные volatile обращения в другое время, когда обработчик не включен.   -  person supercat    schedule 08.07.2016
comment
@DavidSchwartz Вы говорите, что компилятор не может объединить чтения летучих объектов даже в тех случаях, когда ЦП может объединить их? Да, точно. Компилятору в большинстве / во многих случаях запрещается объединять чтение изменчивой информации. ЦП имеет больше информации, доступной ему, и в некоторых случаях может безопасно объединять операции чтения. Например: volatile int* x = 0x10; *x; *x; int y =*x; Компилятор не знает, имеет ли адрес чтения 0x10 побочные эффекты, поэтому он должен выполнять все чтения по порядку. ЦП знает, имеет ли чтение 0x10 побочные эффекты, или, возможно, это регистр только для чтения, и может безопасно объединяться или нет.   -  person mbrig    schedule 11.07.2016
comment
@mbrig Вы не можете делать переносимые утверждения о том, что компилятор знает и чего не знает. Это полный абсурд. Вы говорите, что ЦП, который позволяет запрашивать, какие диапазоны памяти имеют побочные эффекты, не может существовать?   -  person David Schwartz    schedule 11.07.2016
comment
@DavidSchwartz Я не думаю, что это актуально. Стандарты C и C ++ заявляют, что чтение в энергозависимую память является наблюдаемым поведением ((1.9 / 6): наблюдаемое поведение абстрактной машины - это ее последовательность чтения и записи в изменчивые данные и вызовы функций ввода-вывода библиотеки.), Поэтому компилятор должен их сохранить.   -  person mbrig    schedule 11.07.2016
comment
@mbrig То же самое и с ЦП. Стандарты не делают различий между процессором и компилятором. (Как они могли? Они просто говорят, что должно произойти, а не что и как.) Но процессоры Intel совокупно считывают. Так что либо все нарушают стандарт, либо вы его неправильно читаете.   -  person David Schwartz    schedule 11.07.2016
comment
@DavidSchwartz Они, очевидно, не объединяют чтения, когда у него есть побочные эффекты, или когда есть возможность прерывания, изменяющего память за пределами нормального потока (или, если они это делают, они документируют определенные барьеры памяти, которые компилятор должен создать для защиты чтения) . Компилятор и ЦП, очевидно, сохраняют порядок и количество изменчивых операций чтения / записи (где это имеет значение), иначе процедуры обслуживания прерываний не будут работать, а комбинация ЦП / компилятора будет непригодна для многих случаев.   -  person mbrig    schedule 11.07.2016
comment
@mbrig Опять же, этого не может быть. Сохранение вещей там, где это важно, - это правило «как если бы». И volatile является явным исключением из правила «как если бы», поскольку доступ к ним определен как наблюдаемое поведение. Когда вы читаете спецификацию, в ней говорится, что вы не можете прикасаться к ним, когда это не имеет значения. (Опять же, я не согласен с тем, как вы читаете спецификацию. Но вы читаете ее, поскольку доступ к изменчивым переменным определяется как наблюдаемое поведение и поэтому не может быть изменен даже там, где это не имеет значения. Если нет , а затем объясните, что, по вашему мнению, говорится в спецификации.)   -  person David Schwartz    schedule 12.07.2016
comment
Позвольте нам продолжить это обсуждение в чате.   -  person David Schwartz    schedule 12.07.2016


Ответы (1)


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

Для g: из-за x86_64 ABI параметр x из g выделяется в регистр (т.е. rdx) и не имеет места в памяти. Чтение регистра общего назначения не имеет никаких наблюдаемых побочных эффектов, поэтому мертвое чтение исключается.

person avdgrinten    schedule 07.07.2016
comment
Похоже на то, что Ричард Байнер ответил на мой билет - gcc.gnu.org/ bugzilla / show_bug.cgi? id = 71793 - но его ответ и правки заявки, в основном тег wrong-code, указывают на то, что он не думает, что это нормально. Ты? Выделение g(x) в регистре похоже на деталь ABI - в этом случае спасибо за механистическое объяснение - но не на разрешение взлома volatile. Похоже, компилятор должен изменить свое поведение, чтобы в этом случае вести себя должным образом. - person underscore_d; 07.07.2016
comment
А какого поведения вы ожидаете? Компилятор не может выполнить чтение из памяти, потому что в памяти нет места для чтения. Операция чтения абстрактной машины здесь действительно невозможна. Он мог копировать значение в память и читать из этого места, но это имело бы смысл только в том случае, если x действительно мог ускользнуть от g. - person avdgrinten; 07.07.2016
comment
Опять же, если отбросить сомнительную полезность локальной переменной volatile, я ожидаю того же, что и для любого другого volatile-объявленного объекта: чтение и запись должны происходить в памяти и не могут быть исключены или переупорядочены (относительно других объектов того же объекта) . Итак, я ожидал, что x не будет выделен в регистре, а скорее в стеке, как в двух других случаях. Может ли компилятор сделать это, не нарушая ABI? Если так, я думаю, так и должно быть. - person underscore_d; 07.07.2016
comment
Компилятор не может выделить x в памяти, потому что ABI не учитывает изменчивые аргументы (потому что они не имеют особого смысла), а просто передает их в регистры, как энергонезависимые аргументы. Выделение x в памяти нарушит последовательность вызова функций ABI. Обратите внимание, что компилятору будет разрешено копировать x в другое место (но почему чтение / запись в это место должно оставаться упорядоченным / неизменным?), Но он определенно не может принять аргумент x в памяти без нарушения ABI. - person avdgrinten; 07.07.2016
comment
Спасибо за объяснение. То же самое относится к 32-битной x86? У меня нет под рукой 32-битной машины, но кросс-компиляция с MSYS2 MinGW32 дает идентичный ASM ... хотя теперь мне интересно, является ли мой Debian stable ящик, который произвел ASM выше, 32-битным! Я подожду, чтобы увидеть, что сделают люди из GCC, но я думаю, что вы, возможно, правы. Если так, то это, вероятно, просто произошло из-за сочетания соответствия ABI и отсутствия специальной обработки для этого странного сценария - например, копирования в память и использования этого, как вы сказали. Если да, мы посмотрим, добавят ли они (академический) обходной путь для этого. - person underscore_d; 07.07.2016
comment
@avdgrinten: Почему компилятор не может выделить x в памяти? Вызывающий не собирается помещать значение в память, но все это означает, что код пролога функции должен будет это сделать. Если x является изменчивым и функция содержит setjmp, я бы подумал, что компилятору, вероятно, придется сохранить его в памяти и рассматривать его как volatile независимо от того, будет ли выбран его адрес, если компилятор не знает, что setjmp не произойдет неожиданно. - person supercat; 08.07.2016
comment
Я согласен с тем, что компилятор обязательно должен скопировать значение в память (а не исключать доступ к этой ячейке памяти), если функция содержит setjmp или позволяет указателю на локальную изменчивую переменную ускользать от функции. В других случаях мое понимание стандарта таково, что можно исключить изменчивое чтение / запись, даже если переменная не была сохранена в регистре: компилятор ведет себя as-if, изменчивое чтение / запись действительно имело место, потому что он может доказать, что нет one (и даже не аппаратное обеспечение с отображением памяти, обработчики сигналов или другие асинхронные события) действительно могут наблюдать за доступом. - person avdgrinten; 08.07.2016