Директива Delphi Compiler об оценке аргументов в обратном порядке - PullRequest
5 голосов
/ 16 июня 2010

Я был действительно впечатлен этим двухсторонним лайнером delphi, использующим функцию IFThen из Math.pas. Однако сначала он оценивает DB.ReturnFieldI, что, к сожалению, потому что мне нужно вызвать DB.first, чтобы получить первую запись.

DB.RunQuery('select awesomedata1 from awesometable where awesometableid = "great"');
result := IfThen(DB.First = 0, DB.ReturnFieldI('awesomedata1'));

(как бессмысленное разъяснение, потому что у меня уже так много хороших ответов. Я забыл упомянуть, что 0 - это код, который DB.First возвращает, если в нем что-то есть, иначе не могло бы иметь смысла)

Очевидно, что это не такая уж большая проблема, так как я мог бы заставить ее работать с пятью надежными вкладышами. Но все, что мне нужно, чтобы это работало, это чтобы Delphi сначала оценил DB.first, а затем DB.ReturnFieldI. Я не хочу менять math.pas, и я не думаю, что это оправдывает меня, делая перегруженный ifthen, потому что есть как 16 ifthen функций.

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

Ответы [ 4 ]

12 голосов
/ 16 июня 2010

Порядок вычисления выражений обычно undefined . (C и C ++ одинаковы. Java всегда оценивает слева направо.) Компилятор не предоставляет никакого контроля над ним. Если вам нужно вычислить два выражения в определенном порядке, напишите свой код по-другому. Я бы не стал беспокоиться о количестве строк кода. Линии дешевы; используйте столько, сколько вам нужно. Если вам часто приходится пользоваться этим шаблоном, напишите функцию, которая оборачивает все это:

function GetFirstIfAvailable(DB: TDatabaseObject; const FieldName: string): Integer;
begin
  if DB.First = 0 then
    Result := DB.ReturnFieldI(FieldName)
  else
    Result := 0;
end;

Ваш исходный код, вероятно, не был бы тем, что вы хотели, даже если бы порядок оценки был другим. Даже если DB.First не равно равно нулю, вызов ReturnFieldI все равно будет оценен. Все фактические параметры полностью оцениваются перед вызовом функции, которая их использует.

Изменение Math.pas не помогло бы вам в любом случае. Он не контролирует, в каком порядке оцениваются его фактические параметры. К тому времени, как он их видит, они уже были оценены до логического значения и целого числа; они больше не являются исполняемыми выражениями.


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

Соглашение о вызовах pascal передает аргументы слева направо в стеке. Это означает, что крайний левый аргумент - это тот, что находится внизу стека, а самый правый аргумент - вверху, чуть ниже адреса возврата. Если функция IfThen использовала это соглашение о вызовах, компилятор может достичь этого макета стека несколькими способами:

  1. То, как вы ожидаете, что каждый аргумент вычисляется и передается немедленно:

    push (DB.First = 0)
    push DB.ReturnFieldI('awesomedata1')
    call IfThen
    
  2. Оцените аргументы справа налево и сохраните результаты во временных значениях, пока они не будут выдвинуты:

    tmp1 := DB.ReturnFieldI('awesomedata1')
    tmp2 := (DB.First = 0)
    push tmp2
    push tmp1
    call IfThen
    
  3. Сначала выделите пространство стека и оцените в любом удобном порядке:

    sub esp, 8
    mov [esp], DB.ReturnFieldI('awesomedata1')
    mov [esp + 4], (DB.First = 0)
    call IfThen
    

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

Соглашение о вызовах регистров по умолчанию также передает аргументы слева направо, но первые три подходящих аргумента передаются в регистрах. Регистры, используемые для передачи аргументов, также являются регистрами, наиболее часто используемыми для вычисления промежуточных выражений. Результат DB.First = 0 необходимо было передать в регистр EAX, но компилятору также нужен этот регистр для вызова ReturnFieldI и для вызова First. Вероятно, сначала было немного удобнее оценить вторую функцию, например:

call DB.ReturnFieldI('awesomedata1')
mov [ebp - 4], eax // store result in temporary
call DB.First
test eax, eax
setz eax
mov edx, [ebp - 4]
call IfThen

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

3 голосов
/ 16 июня 2010

соглашение о вызовах влияет на то, как они оцениваются.
Не существует компилятора, определяющего это.

Pascal - это соглашение о вызовах, которое вы должны будете использовать, чтобы получить такое поведение.

Хотя я лично никогда не буду зависеть от этого типа поведения.Программа демонстрирует, как это работает.

program Project2;
{$APPTYPE CONSOLE}
uses SysUtils;

function ParamEvalTest(Param : Integer) : Integer;
begin
  writeln('Param' + IntToStr(Param) + ' Evaluated');
  result := Param;
end;

procedure TestStdCall(Param1,Param2 : Integer); stdCall;
begin
  Writeln('StdCall Complete');
end;

procedure TestPascal(Param1,Param2 : Integer); pascal;
begin
  Writeln('Pascal Complete');
end;

procedure TestCDecl(Param1,Param2 : Integer); cdecl;
begin
  Writeln('CDecl Complete');
end;

procedure TestSafecall(Param1,Param2 : Integer); safecall;
begin
  Writeln('SafeCall Complete');
end;

begin
  TestStdCall(ParamEvalTest(1),ParamEvalTest(2));
  TestPascal(ParamEvalTest(1),ParamEvalTest(2));
  TestCDecl(ParamEvalTest(1),ParamEvalTest(2));
  TestSafeCall(ParamEvalTest(1),ParamEvalTest(2));
  ReadLn;
end.

Для этого потребуется написать собственные функции IfThen.

Если вы действительно хотите, чтобы это был один лайнер, вы действительно можете сделать это в Delphi.Я просто думаю, что это выглядит ужасно.

If (DB.First = 0) then result :=  DB.ReturnFieldI('awesomedata1') else result := 0;
1 голос
/ 16 июня 2010

Разве вы не можете изменить свой запрос, чтобы получить только один результат, поэтому избегайте выполнения команды «Первый»? Прямо как:

SELECT TOP 1 awesomedata1 from awesometable 

В доступе ...

0 голосов
/ 16 июня 2010

AFAIK, нет директивы компилятора для управления этим. Если вы не используете соглашения stdcall / cdecl / safecall, параметры передаются в стеке слева направо, но поскольку соглашение о регистрах по умолчанию может также передавать параметры в регистры, может случиться так, что параметр будет вычислен позже и помещен в регистр как раз перед звонком. А поскольку для параметров, которые соответствуют требованиям, фиксированный порядок регистров (EAX, EDX, ECX) установлен, регистры можно загружать в любом порядке. Вы можете попытаться применить «паскальское» соглашение о вызовах (в любом случае вам нужно будет переписать функцию), но IMHO всегда опасно полагаться на такой код, если компилятор не может явно гарантировать порядок оценки. А навязывание порядка оценки может значительно сократить число доступных оптимизаций.

...