Это правильно;запрос указателя в качестве входных данных для встроенного asm не подразумевает, что указанная память также является входом или выходом или и тем, и другим.Имея входной регистр и выходной регистр, gcc знает, что ваш asm просто выравнивает указатель, маскируя младшие биты, или добавляет к нему константу.(В этом случае вы бы захотели оптимизировать мертвое хранилище.)
Простая опция: asm volatile
и "memory"
clobber 1 .
Более узкий, более конкретный способ, который вы запрашиваете, - использовать «фиктивный» операнд памяти , а также указатель в регистре .Ваш шаблон asm не ссылается на этот операнд (за исключением, может быть, внутри комментария asm, чтобы увидеть, что выбрал компилятор).Он сообщает компилятору, какую память вы на самом деле читаете, записываете или читаете + пишете.
Пустой ввод памяти: "m" (*(const int (*)[]) iptr)
или вывод: "=m" (*(int (*)[]) iptr)
.Или, конечно, "+m"
с тем же синтаксисом.
Этот синтаксис приводится к указателю на массив и разыменованию, поэтому фактическим вводом является массив C .(Если у вас действительно есть массив, а не указатель, вам не нужно ничего приводить и вы можете просто запросить его как операнд памяти.)
Если вы оставите размер, не указанный в []
,это говорит GCC, что любая память, к которой обращаются относительно этого указателя, является операндом ввода, вывода или ввода / вывода. Если вы используете [10]
или [some_variable]
, это сообщает компилятору конкретный размер.С переменными размерами во время выполнения gcc на практике пропускает оптимизацию, согласно которой iptr[size+1]
является , а не частью ввода.
GCC документирует это и поэтому поддерживает его.Я думаю, что это не строгое нарушение псевдонимов, если тип элемента массива совпадает с указателем, или, может быть, если он char
.
(из руководства GCC)
Пример x86где строковый аргумент памяти имеет неизвестную длину.
asm("repne scasb"
: "=c" (count), "+D" (p)
: "m" (*(const char (*)[]) p), "0" (-1), "a" (0));
Если вы не можете использовать ранний клоббер для операнда ввода указателя, операнд ввода фиктивной памяти обычно выбирает простой режим адресации, используятот же регистр.
Но если вы используете ранний клоббер для строгой правильности цикла asm, иногда фиктивный операнд будет выполнять инструкции gcc-траты (и дополнительный регистр) на базовом адресе для операнда памяти,Проверьте asm output компилятора.
Background:
Это широко распространенная ошибка в примерах inline-asm, которая часто остается незамеченной, потому что asm обернутв функции, которая не встраивается ни в какие вызывающие объекты, которые соблазняют компилятор переупорядочивать хранилища для слияния, выполняя удаление мертвых хранилищ.
Синтаксис встроенного ассемблера GNU C разработан вокруг описания одиночной инструкциикомпилятору.Намерение состоит в том, что вы сообщаете компилятору о вводе памяти или выводе памяти с ограничением операнда "m"
или "=m"
, и он выбирает режим адресации.
Для записи целых циклов во встроенном ассемблере требуетсяубедитесь, что компилятор действительно знает, что происходит (или asm volatile
плюс "memory"
clobber), в противном случае вы рискуете выйти из строя при изменении окружающего кода или включите оптимизацию во время компоновки, которая допускает встраивание между файлами.
См. Также Зацикливание массивов со встроенной сборкой для использования оператора asm
в качестве цикла body , продолжая выполнять логику цикла в C. С фактическим (не фиктивным) "m"
и "=m"
операнды, компилятор может развернуть цикл, используя смещения в выбранных им режимах адресации.
Сноска 1: "memory"
clobber заставляет компилятор обрабатывать asm как вызов не встроенной функции (который может читать или записывать любую память, кроме локальных, которые, как доказал escape-анализ , не избежали ). Экранирующий анализ включает в себя входные операнды самого оператора asm, а также любые глобальные или статические переменные, в которых любой предыдущий вызов мог хранить указатели. Поэтому обычно счетчики локальных циклов не нужно разливать / перезагружать вокруг оператора asm
с помощью "memory"
clobber.
asm volatile
необходимо, чтобы удостовериться, что asm не оптимизирован, даже если его выходные операнды не используются (потому что вы требуете, чтобы не был объявлен побочный эффект записи памяти).
Или для памяти, которая читается только asm, вам нужно запустить asm снова, если один и тот же входной буфер содержит разные входные данные. Без volatile
оператор asm может быть CSEd вне цикла. (A "memory"
clobber не заставляет оптимизатор обрабатывать всю память как входные данные при рассмотрении необходимости выполнения оператора asm
.)
asm
без выходных операндов неявно volatile
, но это хорошая идея, чтобы сделать его явным. (В руководстве GCC есть раздел as volatile ).
например. asm("... sum an array ..." : "=r"(sum) : "r"(pointer), "r"(end_pointer) : "memory")
имеет выходной операнд, поэтому не является неявно изменяемым. Если вы использовали это как
arr[5] = 1;
total += asm_sum(arr, len);
memcpy(arr, foo, len);
total += asm_sum(arr, len);
Без volatile
2-й asm_sum
мог бы оптимизироваться, предполагая, что один и тот же asm с одинаковыми входными операндами (указатель и длина) будет выдавать одинаковый вывод. Вам нужен volatile
для любого asm, который не является чистой функцией его явных входных операндов. Если он не оптимизируется, , тогда клоббер "memory"
будет иметь желаемый эффект, требующий синхронизации памяти.