Я был действительно впечатлен этим Дельфи два лайнера с помощью функции 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 и вслепую получить первую вещь, которую он находит, не настоящий программист.
Порядок оценки выражений обычно не определен. (В 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
используется такое соглашение о вызове, компилятор мог бы добиться такого расположения стека несколькими способами:
Способ, который вы ожидаете, то есть каждый аргумент оценивается и заталкивается немедленно:
push (DB.First = 0)
push DB.ReturnFieldI('awesomedata1')
вызов IfThen
Оцениваем аргументы справа налево и храним результаты во временных файлах до тех пор, пока они не будут вытолкнуты:
tmp1 := DB.ReturnFieldI('awesomedata1')
tmp2 := (DB.First = 0)
push tmp2
нажать на tmp1
вызов IfThen
Сначала выделяем место в стеке, и оцениваем в любом удобном порядке:
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
с нулем.
Разве вы не можете изменить свой запрос так, чтобы он давал только один результат, поэтому избегайте выполнения команды «Первый»? Например:
SELECT TOP 1 awesomedata1 from awesometable
Доступен ...
Соглашение о вызовах влияет на способ их оценки.
Не существует компилятора, который бы контролировал это.
Паскаль
- это соглашение о вызовах, которое вы должны использовать, чтобы получить такое поведение.
Хотя лично я бы никогда не стал зависеть от такого поведения.
В следующем примере программы показано, как это работает.
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;
AFAIK не существует директивы компилятора для управления этим. Если вы не используете конвенции stdcall/cdecl/safecall, параметры передаются слева направо по стеку, но поскольку стандартная конвенция регистров может передавать параметры и в регистрах, может случиться так, что параметр будет вычислен позже и помещен в регистр непосредственно перед вызовом. И поскольку для параметров, которые квалифицируются, фиксирован только порядок регистров (EAX, EDX, ECX), регистры могут быть загружены в любом порядке. Вы можете попытаться навязать "паскалевый" порядок вызова (в любом случае вам придется переписать функцию), но IMHO всегда опасно полагаться на такой код, если компилятор не может явно гарантировать порядок оценки. А навязывание порядка оценки может сильно сократить количество доступных оптимизаций.