Встроенная сборка внутри циклов

Я широко использую встроенную сборку в проекте, где мне нужно вызывать функции с неизвестным количеством аргументов во время компиляции, и пока мне удается заставить ее работать, иногда в Linux (в Windows я не помню, чтобы была такая проблема) происходят такие странные вещи:

Если у меня есть что-то вроде

for(int i = 1; i >= 0; i--)
   asm("push %0"::"m"(someArray[i]));

Оно работает.

Если у меня есть

for(int i = this->someVar; i >= 0; i--)
   asm("push %0"::"m"(someArray[i]));

и я гарантирую своей жизнью, что someVar имеет значение 1, которое вызывает ошибку сегментации.

Также, если у меня есть

int x = 1;
for(int i = x; i >= 0; i--)
   asm("push %0"::"m"(someArray[i]));

это работает, но

int x = this->someVar;
for(int i = x; i >= 0; i--)
    asm("push %0"::"m"(someArray[i]));

не.

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

Если кто-то может указать мне некоторую информацию, которая может прояснить, в чем проблема, я был бы признателен.

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

Я также пытался использовать встроенное ассемблерное слово «volatile», но ничего не изменилось.


person user246100    schedule 13.01.2010    source источник
comment
Наверняка возникает вопрос, почему бы не передать указатель на массив вместо копирования элементов массива в стек, или я что-то упустил? В соответствии с: func (someArray, this-›someVar), а затем вы также получите прирост производительности (без перемещения памяти, без цикла)   -  person Skizz    schedule 13.01.2010
comment
Я вызываю функции, над которыми у меня нет контроля. Они есть в других библиотеках. Мне нужно передать им аргументы, как они их ожидают.   -  person user246100    schedule 13.01.2010
comment
Изменение относительного положения локальных переменных путем выполнения непарного push прямо под руками компилятора — очень и очень плохая идея.   -  person avakar    schedule 13.01.2010
comment
Вы имеете в виду: то, что вы делаете, это очень плохая идея? Честно говоря, я только начал изучать C++ и ассемблер с ноября, так что я новичок в обоих вопросах.   -  person user246100    schedule 13.01.2010


Ответы (5)


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

asm{
   loop1:
     mov ax, this->var
     ...
     dec ax
     cmp ax, 0
     je exit
     jmp loop1
}

...

выход:

Также попробуйте сделать значение «var» статическим, это тоже может помочь.

person oivoodoo    schedule 13.01.2010
comment
Он не может быть статичным из-за того, как работает код. Я собираюсь попробовать сделать код только на ассемблере. Спасибо. - person user246100; 13.01.2010

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

Вы можете попробовать различные обходные пути (например, объявить локальные переменные register), но, к сожалению, в этом случае нет хорошего способа гарантировать правильное поведение в C/C++. Чтобы избежать этой проблемы, реализуйте цикл самостоятельно, как предлагает oivoodoo.

person moonshadow    schedule 13.01.2010
comment
Спасибо, я собираюсь попробовать предложение oivoodoo - person user246100; 13.01.2010

Вот мои попытки психической отладки:

i и this, скорее всего, хранятся в стеке, а на 386 и более поздних версиях машинный код может напрямую обращаться к esp-относительным ячейкам памяти, поэтому компилятор вполне может создавать такие инструкции, как

mov eax,[esp+8]

чтобы получить значение this в регистр eax. Проблема в том, что ваши push операции путаются с указателем стека, поэтому эти жестко запрограммированные обращения будут обращаться (все чаще) к неправильным ячейкам памяти после первой итерации.

Скорее всего, более простые формы циклов без this->someVar оптимизируются компилятором более тщательно и приводят к тому, что машинный код использует только регистры и не использует esp-относительный доступ, то есть они продолжают нормально работать.

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

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

person j_random_hacker    schedule 13.01.2010
comment
Хотя у меня нет ваших знаний по этому вопросу, мои наблюдения и то, что я знаю, заставляют меня думать, что то, что происходит, похоже на то, что вы говорите. Спасибо за полезный комментарий. - person user246100; 13.01.2010

Во-первых, что, вероятно, происходит, так это то, что gcc под linux использует указатель стека для индексации ваших локальных переменных, а не использует указатель фрейма стека. Это оптимизация, которая позволяет gcc использовать указатель фрейма (BP в x86) в качестве еще одного регистра общего назначения и избежать большого количества кода, устанавливающего фреймы. Кадры — это, по сути, просто область между SP и BP, принадлежащая локальной функции. Держу пари, что если бы вы включили вызов alloca с размером, который вы передали в эту функцию, все стало бы лучше, потому что это заставило бы компилятор не выполнять эту оптимизацию.

При этом ошибка действительно в вашем коде. Если вы действительно не знаете, что делаете, вы никогда не должны выходить из встроенного asm с указателем стека, отличным от того, который был при входе во встроенный asm. Компиляторы почти всегда думают, что они владеют исключительно указателем стека. Они полагаются на то, что он останется неизменным, чтобы они могли использовать его, чтобы найти, где они сохранили переменные. Вы также должны держаться подальше от указателя кадра (BP).

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

person nategoose    schedule 26.03.2010

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

Предупреждение: abi x86_64 использует регистр для некоторых параметров, что нарушает это и ваш код.

person Alex Brown    schedule 13.01.2010
comment
Количество не ограничено, и я хочу, чтобы это было так. Я полагаю, что ваше решение будет менее эффективным. Что касается вопроса x86_64, на который вы ссылаетесь, это не проблема только потому, что приложение для него неприменимо. - person user246100; 13.01.2010