Как вернуть сложное возвращаемое значение? - PullRequest
5 голосов
/ 13 сентября 2010

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

Я могу подумать о возвращении адреса в EAX, который указывает на реальное значение в памяти.Но это стандартный способ?

Большое спасибо ~~~

Ответы [ 7 ]

10 голосов
/ 13 сентября 2010

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

Например, на платформе x86, когда арифметика с плавающей запятой обрабатывается инструкциями FPU, результатфункция возвращается как верхнее значение в стеке регистров FPU.(Если вы знаете, регистры x86 FPU организованы в своего рода «круговой стек»).В этот момент это не float и double, это значение, хранящееся с внутренней точностью FPU (которое может быть выше float или double), и вызывающая сторона несет ответственность за получение этого значения сверхустека FPU и преобразовать его в любой тип, который он пожелает.Фактически, именно так работает типичная инструкция FPU: она берет свои аргументы с вершины стека FPU и переносит результат обратно в стек FPU.Реализуя свою функцию таким же образом, вы по существу эмулируете «сложную» инструкцию FPU с вашей функцией - довольно естественный способ сделать это.

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

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

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

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

6 голосов
/ 13 сентября 2010

В качестве 1-го элемента в стеке должен быть возвращен double.

Вот пример кода C ++ (x86):

double sqrt(double n)
{
    _asm fld n
    _asm fsqrt
}  

Если вы предпочитаете управлять стеком вручную (сохранение некоторых циклов ЦП):

double inline __declspec (naked) __fastcall sqrt(double n)
{
    _asm fld qword ptr [esp+4]
    _asm fsqrt
    _asm ret 8
}

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

2 голосов
/ 14 сентября 2010

Если у вас есть вопросы по поводу соглашений о вызовах или языка ассемблера, напишите простую функцию на языке высокого уровня (в отдельном файле). Затем, пусть ваш компилятор сгенерирует листинг на ассемблере или ваш отладчик отобразит «чередующуюся сборку».

Листинг не только расскажет вам, как компилятор реализует код, но также покажет вам соглашения о вызовах. Намного проще, чем отправлять сообщения в S.O. и обычно быстрее. ; -)

1 голос
/ 14 сентября 2010

Это зависит от ABI.Например, Linux на x86 использует ABI Sys V, указанный в Дополнение к процессору архитектуры Intel386, четвертое издание .

Раздел Последовательность вызова функций * Раздел 1006 * содержит информациюо том, как значения должны быть возвращены.Вкратце, в этом API:

  • Функции, возвращающие скаляры или не использующие значения %eax;
  • Функции, возвращающие значения с плавающей запятой, используют %st(0);
  • Для функцийвозвращая типы struct или union, вызывающая сторона предоставляет пространство для возвращаемого значения и передает свой адрес в качестве скрытого первого аргумента.Вызываемый абонент возвращает этот адрес в %eax.
1 голос
/ 13 сентября 2010

C99 имеет сложный встроенный тип данных (_Complex). Поэтому, если у вас есть C99-совместимый компилятор, вы можете просто скомпилировать некоторую функцию, которая возвращает комплекс, и скомпилировать ее в ассемблер (обычно с опцией -S). Там вы можете увидеть соглашение, которое принято.

0 голосов
/ 13 сентября 2010

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

0 голосов
/ 13 сентября 2010

Обычно вы используете стек

...