Директива компилятора 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 и вслепую получить первую вещь, которую он находит, не настоящий программист.

5
задан Peter Turner 16 June 2010 в 19:55
поделиться

4 ответа

Порядок оценки выражений обычно не определен. (В 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')
    вызов IfThen
    
  2. Оцениваем аргументы справа налево и храним результаты во временных файлах до тех пор, пока они не будут вытолкнуты:

    tmp1 := DB.ReturnFieldI('awesomedata1')
    tmp2 := (DB.First = 0)
    push tmp2
    нажать на tmp1
    вызов 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 с нулем.

12
ответ дан 18 December 2019 в 10:42
поделиться

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

SELECT TOP 1 awesomedata1 from awesometable 

Доступен ...

1
ответ дан 18 December 2019 в 10:42
поделиться

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

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

Хотя лично я бы никогда не стал зависеть от такого поведения.

В следующем примере программы показано, как это работает.

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;
3
ответ дан 18 December 2019 в 10:42
поделиться

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

0
ответ дан 18 December 2019 в 10:42
поделиться
Другие вопросы по тегам:

Похожие вопросы: